Recurring todos in HabitRPG via PHP.

/Recurring todos in HabitRPG via PHP.

So some of you may remember a while back I started “playing” (using?) HabitRPG. Habit is basically a cross between a task management app and an RPG game. Instead of things like “kill ten rats”, your quests are things you define from your own todo lists, like “go to the gym” or “write your goddamn book you lazy bastard”.

Habit has three types of tasks: habits are ad hoc things you want to either do (giving rewards), or avoid (bringing pain); dailies are things you want to do either every day, or every Tuesday, or every Sunday and Wednesday, or whatever; and to dos are “projects” you want to do at some point, then tick off.

One of the downsides of Habit is that it doesn’t support recurring tasks that aren’t liked to a particular day of the week. So, for example, I have a daily to take the bins out every Sunday (because that’s when garbage collection is, and it doesn’t make sense to do it on another day), and a habit for washing the dishes (I won’t cry if it’s not done every day… but I should do it). But what about, say, re-dying my hair, which is something I generally want to do on the first Friday of every second month, although if it waits a few weeks it doesn’t matter much (I’ll just suffer through a faded dye job)? Or what about my gym schedule, which is alternating weights and cardio, though, again, doesn’t really matter if I don’t go literally every single day?

Yeah. This can get complicated real quick.

Fortunately, Habit has an API, and I know some PHP scripting. So I whipped up something cute and dinky containing all my silly task auto-creation logic, and run it once a day with cron.

The foundation of the script is a very simple class to access the Habit API, which looks like:

class HabitRPG {
private $usr = 'YOUR USERID HERE';
private $key = 'YOUR API KEY HERE';

private $api = 'https://habitrpg.com:443/api/v2/';

private $tasks;
private $stats;

// get task by ID
private function getTask( $id ){
  return $this->habitGet( 'user/tasks/'. $id );
}

// get task completion by ID
public function getTaskCompleted( $id ){
  $t = $this->getTask( $id );
  if( isset( $t->completed ) )
    { return $t->completed; }
  else
  { return FALSE; }
}

// get task streak
// we need this because, for some reason, dailies don't increase their streak if completed by the API
// this should maybe be abstracted with the above but whatevs
public function getTaskStreak( $id ){
  $t = $this->getTask( $id );
  if( isset( $t->streak ) )
    { return $t->streak; }
  else
    { return FALSE; } // if we have no $t->streak we're a non-daily, so return false (rather than 0)
}

// search tasks by task name
// default to todos, with any sort of completion (0: incomplete, 1: complete, FALSE: don't care)
public function searchTasks( $title, $complete = FALSE, $type = 'todo' ){

  foreach( $this->tasks as $t ){
    if( $complete === FALSE ){
      if( $t->text == $title && $t->type == $type ) { return TRUE; }
    } elseif( $complete === 0 ){
      if( $t->text == $title && $t->type == $type && !isset( $t->dateCompleted ) ) { return TRUE; }
    } elseif( $complete === 1 ){
      if( $t->text == $title && $t->type == $type && isset( $t->dateCompleted ) ) { return TRUE; }
    }
  }

  return FALSE;
}

// get stuff
private function habitGet( $api ){
  $ch = curl_init();
  curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
    'x-api-user: '. $this->usr,
    'x-api-key: '. $this->key
  ));

  curl_setopt( $ch, CURLOPT_URL, $this->api . $api );
  curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );

  $r = json_decode( curl_exec( $ch ) );
  curl_close( $ch );

  return $r;
}

// update stuff
public function habitPost( $api, $data = FALSE ){
  $ch = curl_init( 'https://habitrpg.com:443/api/v2/'. $api );

  if( $data !== FALSE ){
    $data = json_encode( $data );
    curl_setopt( $ch, CURLOPT_POSTFIELDS, $data );
  }
  curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'x-api-user: '. $this->usr,
    'x-api-key: '. $this->key
  ));
  curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );

  $result = curl_exec( $ch );
  return json_decode( $result );
}

// some updates use PUT for some reason... IDEK either
public function habitPut( $api, $data ){
  $data = json_encode( $data );

  $ch = curl_init( 'https://habitrpg.com:443/api/v2/'. $api );

  curl_setopt( $ch, CURLOPT_POSTFIELDS, $data );
  curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
  curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'X-HTTP-Method-Override: PUT',
    'x-api-user: '. $this->usr,
    'x-api-key: '. $this->key
  ) );

  $result = curl_exec( $ch );
  return json_decode( $result );
}
}

Note that your userID and API key can be found via Settings › API.

That’s very basic, but it should get you started. Use it like so:

// if it's monday, survive!
$habit = new HabitRPG();
$taskName = 'Survive the day!';
if( date( 'N' ) == 1 ){
  // because this is a todo, check we don't already have an incomplete todo with the same name
  if( !$habit->searchTasks( $taskName, 0 ) ){

    // start building our new task
    $todo = array(
      'text' => $taskName,
      'type' => 'todo',
    );

    // send it to habit
    $r = $habit->habitPost( 'user/tasks', $todo );
  }
}

And that’s basically it. You can get far more adventurous, if you want, as alluded to above with the “different todo every second day depending on what the last todo was we completed in the sequence” gym example. You can also get much more creative building the tasks, assigning them priorities, tags, descriptions, stat affinities… basically anything you can do in the web UI.

(That being said, I wouldn’t say the documentation around this is great; mostly I use the API web interface to inspect what my already-created tasks look like, and use that to build my script-created tasks.)

Scripting Habit also allows for some funky interactions with other services, particularly with regards to habit completion. For example, I have a daily that checks WordPress to see if I blogged yesterday. If I did, it auto-completes:

$taskID = 'WHATEVER YOUR TASK ID IS';
$habit = new HabitRPG();
if( $habit->getTaskCompleted( $taskID ) === FALSE ){
  $wp = new WordPress();
  if( ( time() - $wp->getLastPost() ) < ( 60 * 60 * 24 ) ){     // we have to update our streak manually...
     $todo = array(
      'completed' => true,
      'streak' => $habit->getTaskStreak( $taskID ) + 1,
    );

    // send it to habit
    $r = $habit->habitPut( 'user/tasks/'. $taskID, $todo );
  }
}

(The next step will be linking Habit to Fitbit, to auto-complete dailies when I reach my step goal, but this is trickier because Fitbit’s API is OAuth, and OAuth is much more of a pain in the ass to script against because of the interactive login. But whatever; the point is, you can pretty much link Habit to any web service with an API, and that’s kinda rad.)

Note that, in the above, we hit our first gotcha; daily streaks don’t auto-increment using the API. We have to send the value manually. This one also requires that we know the task ID, which I just grab from the API UI and hardcode into the script. Also, WordPress() here is just a little class that calls GET https://public-api.wordpress.com/rest/v1/sites/$site/posts/?number=1 and returns TRUE or FALSE depending on whether the post date was within the last 24 hours.

The second gotcha I’ve found in the API is around casting spells/using abilities. There’s technically a call for this, and I can get it working using command-line cURL, but not, for whatever reason, with PHP cURL (it just does and returns nothing). Which is a shame, because I think every class in Habit has that 10 MP “daily” spell which is good to cast on your reddest/bluest task, and which I always forget to cast manually.

Oh well. I guess I can’t have everything play automatically…

2018-05-22T09:00:51+00:0016th August, 2014|Tags: php, programming, tech, xp|Comments Off on Recurring todos in HabitRPG via PHP.