Clip to Evernote

Don't Panic!Garmin Connect export to Dailymile

Overview

I use dailymile, and I use Garmin Connect. Only problem is if you use a Garmin device which uses the new .FIT formats, or an older device which isn't supported by the Garmin Communicator Plugin, you can't upload from these devices to DM. As I use GC for all of my Garmin devices, the next logical step is to go there, export my route .GPX, then head over to DM and create a workout entry manually, then create a new track and upload my .GPX file for it. Yeah. That's fun...

So here's my solution. No reason whatsoever that we can't automate this whole thing.

Intro

This is not the cleanest code, I know that. Don't bother emailing me telling me that. This entire page is just meant to show you the base of how to do something, it's up to you to take and recode it to work the way that you want it to. I welcome improvements and such to the examples (especially from GC folks who know little things that aren't documented. :D). Please, also do not email me letting me know that I'm validating JSON in one section of code, and not the next. Like I said, it's meant for you to customize to your own liking.

Just a little bit of history on this script. This kind of fell off my radar about a year ago, as I was waiting for the GC team to finish working on the API. They went through some team changes and the API development seems to have fallen by the wayside, and I've been waiting for over a year now. So this is just my solution for the time being.

I did this in PHP so that it *should* run on any platform as long as your PHP installation supports CURL. I'll walk through the different interfaces that we do and put the entire script at the end of the page for you.

Garmin Connect

As I said, GC went through some team changes and the API development seems to have halted all together. Without OAuth or API access, we need to do a few CURL calls in order to get the cookies that we need to get our activities information. This is actually pretty painless. We just call the login form to get a valid session cookie, and then login to GC and we're done.

<?php
// Set your username and password for Garmin Connect here.
$username = 'username';
$password = 'password';

$urlGCLogin    = 'http://connect.garmin.com/signin';
$urlGCSearch   = 'http://connect.garmin.com/proxy/activity-search-service-1.0/json/activities?';
$urlGCActivity = 'http://connect.garmin.com/proxy/activity-service-1.1/gpx/activity/';

// Initially, we need to get a valid session cookie, so we pull the login page.
curl( $urlGCLogin );

// Now we'll actually login
curl( $urlGCLogin . '?login=login&login:signInButton=Sign%20In&javax.faces.ViewState=j_id1&login:loginUsernameField='.$username.'&login:password='.$password.'&login:rememberMe=on');
?>

At this point, you're logged into GC and you'll have your cookies in the /tmp/cookies.txt file. Feel free to take a look.

Now we'll search for our latest activity. As I'm calling this from a command line script, I'm going to accept a parameter to tell it how many activities to go back on GC to export.

<?php
// Now we search GC for the latest activity.
// We support calling multiples from command line if specified,
// otherwise, only pull the last activity.
if ( ! empty( $argc ) && ( is_numeric( $argv[1] ) ) ) {
	$search_opts = array(
		'start' => 0,
		'limit' => $argv[1]
		);
} else {
	$search_opts = array(
		'start' => 0,
		'limit' => 1
		);
}

// Call the search
$result = curl( $urlGCSearch . http_build_query( $search_opts ) );

// Decode our results
$json = json_decode( $result );

// Make sure they're valid
if ( ! $json ) {
	echo "Error: ";	
	switch(json_last_error()) {
		case JSON_ERROR_DEPTH:
			echo ' - Maximum stack depth exceeded';
			break;
		case JSON_ERROR_CTRL_CHAR:
			echo ' - Unexpected control character found';
			break;
		case JSON_ERROR_SYNTAX:
			echo ' - Syntax error, malformed JSON';
			break;
	}
	echo PHP_EOL;
	var_dump( $result );
	die();
}

// Pull out just the list of activites
$activities = $json->{'results'}->{'activities'};

// Process each activity.
foreach ( $activities as $a ) {

	// Do something with each here.
}

?>

So now we have our results. We've pulled out the activities, you can do a simple var_dump( $a ) and see the information that you now have to work with for the activity. It basically gives you the following (these aren't all, but most of the relevant ones that I would think folks would use):

$a->{'activity'}->{'activityId'}

$a->{'activity'}->{'activityName'}->{'value'}

$a->{'activity'}->{'activityDescription'}->{'value'}

$a->{'activity'}->{'locationName}->{'value'}

$a->{'activity'}->{'userId'}
$a->{'activity'}->{'username'}

$a->{'activity'}->{'uploadDate'}->{'display'}
$a->{'activity'}->{'uploadDate'}->{'value'}
$a->{'activity'}->{'uploadDate'}->{'withDay'}
$a->{'activity'}->{'uploadDate'}->{'abbr'}
$a->{'activity'}->{'uploadDate'}->{'millis'}

$a->{'activity'}->{'uploadedWith'}->{'key'}
$a->{'activity'}->{'uploadedWith'}->{'display'}
$a->{'activity'}->{'uploadedWith'}->{'displaySingular'}
$a->{'activity'}->{'uploadedWith'}->{'version'}

$a->{'activity'}->{'device'}->{'key'}
$a->{'activity'}->{'device'}->{'display'}
$a->{'activity'}->{'device'}->{'displaySingular'}
$a->{'activity'}->{'device'}->{'version'}

$a->{'activity'}->{'privacy'}->{'key'}
$a->{'activity'}->{'privacy'}->{'display'}

$a->{'activity'}->{'activityType'}->{'key'}
$a->{'activity'}->{'activityType'}->{'display'}
$a->{'activity'}->{'activityType'}->{'parent'}->{'key'}
$a->{'activity'}->{'activityType'}->{'parent'}->{'display'}
$a->{'activity'}->{'activityType'}->{'type'}->{'key'}
$a->{'activity'}->{'activityType'}->{'type'}->{'display'}

$a->{'activity'}->{'eventType'}->{'key'}
$a->{'activity'}->{'eventType'}->{'display'}

$a->{'activity'}->{'activityTimeZone'}->{'key'}
$a->{'activity'}->{'activityTimeZone'}->{'offset'}
$a->{'activity'}->{'activityTimeZone'}->{'display'}
$a->{'activity'}->{'activityTimeZone'}->{'abbr'}

$a->{'activity'}->{'beginLatitude'}->{'display'}
$a->{'activity'}->{'beginLatitude'}->{'value'}
$a->{'activity'}->{'beginLatitude'}->{'withUnit'}
$a->{'activity'}->{'beginLatitude'}->{'withUnitAbbr'}
$a->{'activity'}->{'beginLatitude'}->{'uom'}
$a->{'activity'}->{'beginLatitude'}->{'unitAbbr'}

$a->{'activity'}->{'beginLongitude'}->{'display'}
$a->{'activity'}->{'beginLongitude'}->{'value'}
$a->{'activity'}->{'beginLongitude'}->{'withUnit'}
$a->{'activity'}->{'beginLongitude'}->{'withUnitAbbr'}
$a->{'activity'}->{'beginLongitude'}->{'uom'}
$a->{'activity'}->{'beginLongitude'}->{'unitAbbr'}

$a->{'activity'}->{'endLatitude'}->{'display'}
$a->{'activity'}->{'endLatitude'}->{'value'}
$a->{'activity'}->{'endLatitude'}->{'withUnit'}
$a->{'activity'}->{'endLatitude'}->{'withUnitAbbr'}
$a->{'activity'}->{'endLatitude'}->{'uom'}
$a->{'activity'}->{'endLatitude'}->{'unitAbbr'}

$a->{'activity'}->{'endLongitude'}->{'display'}
$a->{'activity'}->{'endLongitude'}->{'value'}
$a->{'activity'}->{'endLongitude'}->{'withUnit'}
$a->{'activity'}->{'endLongitude'}->{'withUnitAbbr'}
$a->{'activity'}->{'endLongitude'}->{'uom'}
$a->{'activity'}->{'endLongitude'}->{'unitAbbr'}

//Average Pace
$a->{'activity'}->{'weightedMeanSpeed'}->{'display'}
$a->{'activity'}->{'weightedMeanSpeed'}->{'value'}
$a->{'activity'}->{'weightedMeanSpeed'}->{'withUnit'}
$a->{'activity'}->{'weightedMeanSpeed'}->{'withUnitAbbr'}
$a->{'activity'}->{'weightedMeanSpeed'}->{'uom'}
$a->{'activity'}->{'weightedMeanSpeed'}->{'unitAbbr'}

//Average Moving Pace
$a->{'activity'}->{'weightedMeanMovingSpeed'}->{'display'}
$a->{'activity'}->{'weightedMeanMovingSpeed'}->{'value'}
$a->{'activity'}->{'weightedMeanMovingSpeed'}->{'withUnit'}
$a->{'activity'}->{'weightedMeanMovingSpeed'}->{'withUnitAbbr'}
$a->{'activity'}->{'weightedMeanMovingSpeed'}->{'uom'}
$a->{'activity'}->{'weightedMeanMovingSpeed'}->{'unitAbbr'}

//Best Pace
$a->{'activity'}->{'maxSpeed'}->{'display'}
$a->{'activity'}->{'maxSpeed'}->{'value'}
$a->{'activity'}->{'maxSpeed'}->{'withUnit'}
$a->{'activity'}->{'maxSpeed'}->{'withUnitAbbr'}
$a->{'activity'}->{'maxSpeed'}->{'uom'}
$a->{'activity'}->{'maxSpeed'}->{'unitAbbr'}

//Calories
$a->{'activity'}->{'sumEnergy'}->{'display'}
$a->{'activity'}->{'sumEnergy'}->{'value'}
$a->{'activity'}->{'sumEnergy'}->{'withUnit'}
$a->{'activity'}->{'sumEnergy'}->{'withUnitAbbr'}
$a->{'activity'}->{'sumEnergy'}->{'uom'}
$a->{'activity'}->{'sumEnergy'}->{'unitAbbr'}

$a->{'activity'}->{'sumElapsedDuration'}->{'display'}
$a->{'activity'}->{'sumElapsedDuration'}->{'value'}
$a->{'activity'}->{'sumElapsedDuration'}->{'withUnit'}
$a->{'activity'}->{'sumElapsedDuration'}->{'withUnitAbbr'}
$a->{'activity'}->{'sumElapsedDuration'}->{'uom'}
$a->{'activity'}->{'sumElapsedDuration'}->{'unitAbbr'}

$a->{'activity'}->{'sumMovingDuration'}->{'display'}
$a->{'activity'}->{'sumMovingDuration'}->{'value'}
$a->{'activity'}->{'sumMovingDuration'}->{'withUnit'}
$a->{'activity'}->{'sumMovingDuration'}->{'withUnitAbbr'}
$a->{'activity'}->{'sumMovingDuration'}->{'uom'}
$a->{'activity'}->{'sumMovingDuration'}->{'unitAbbr'}

$a->{'activity'}->{'sumDuration'}->{'display'}
$a->{'activity'}->{'sumDuration'}->{'minutesSeconds}

$a->{'activity'}->{'sumDistance'}->{'display'}
$a->{'activity'}->{'sumDistance'}->{'value'}
$a->{'activity'}->{'sumDistance'}->{'withUnit'}
$a->{'activity'}->{'sumDistance'}->{'withUnitAbbr'}
$a->{'activity'}->{'sumDistance'}->{'uom'}
$a->{'activity'}->{'sumDistance'}->{'unitAbbr'}

$a->{'activity'}->{'maxElevation'}->{'display'}
$a->{'activity'}->{'maxElevation'}->{'value'}
$a->{'activity'}->{'maxElevation'}->{'withUnit'}
$a->{'activity'}->{'maxElevation'}->{'withUnitAbbr'}
$a->{'activity'}->{'maxElevation'}->{'uom'}
$a->{'activity'}->{'maxElevation'}->{'unitAbbr'}

$a->{'activity'}->{'minElevation'}->{'display'}
$a->{'activity'}->{'minElevation'}->{'value'}
$a->{'activity'}->{'minElevation'}->{'withUnit'}
$a->{'activity'}->{'minElevation'}->{'withUnitAbbr'}
$a->{'activity'}->{'minElevation'}->{'uom'}
$a->{'activity'}->{'minElevation'}->{'unitAbbr'}

$a->{'activity'}->{'gainElevation'}->{'display'}
$a->{'activity'}->{'gainElevation'}->{'value'}
$a->{'activity'}->{'gainElevation'}->{'withUnit'}
$a->{'activity'}->{'gainElevation'}->{'withUnitAbbr'}
$a->{'activity'}->{'gainElevation'}->{'uom'}
$a->{'activity'}->{'gainElevation'}->{'unitAbbr'}

$a->{'activity'}->{'beginTimestamp'}->{'display'}
$a->{'activity'}->{'beginTimestamp'}->{'value'}
$a->{'activity'}->{'beginTimestamp'}->{'withDay'}
$a->{'activity'}->{'beginTimestamp'}->{'abbr'}
$a->{'activity'}->{'beginTimestamp'}->{'millis'}

$a->{'activity'}->{'endTimestamp'}->{'display'}
$a->{'activity'}->{'endTimestamp'}->{'value'}
$a->{'activity'}->{'endTimestamp'}->{'withDay'}
$a->{'activity'}->{'endTimestamp'}->{'abbr'}
$a->{'activity'}->{'endTimestamp'}->{'millis'}

$a->{'activity'}->{'lossElevation'}->{'display'}
$a->{'activity'}->{'lossElevation'}->{'value'}
$a->{'activity'}->{'lossElevation'}->{'withUnit'}
$a->{'activity'}->{'lossElevation'}->{'withUnitAbbr'}
$a->{'activity'}->{'lossElevation'}->{'uom'}
$a->{'activity'}->{'lossElevation'}->{'unitAbbr'}

So now that you have all of this information about your GC activity, you still need to pull down the .GPX file that will contain your GPS route data if there is any.

<?php
$gpx_filename = './activities/activity_' . $a->{'activity'}->{'activityId'} . '.gpx';
$save_file = fopen( $gpx_filename, 'w+' );
$curl_opts = array(
	CURLOPT_FILE => $save_file
	);
curl( $urlGCActivity . $a->{'activity'}->{'activityId'} . '?full=true', array(), array(), $curl_opts );
fclose( $save_file );
?>

Now we'll validate our .GPX file. If we don't actually have any GPS data, such as a trainer ride or treadmill, then we really don't need to upload a GPS track, or even create a track on DM.

<?php
$gpx = simplexml_load_file( $gpx_filename, 'SimpleXMLElement', LIBXML_NOCDATA );
$gpxupload = ( count( $gpx->trk->trkseg->trkpt ) > 0);
?>

That was easy, huh? :) So now if $gpxupload then we'll upload the GPX file to DM.

Dailymile

I'm not going to dig too far into the DM side of things as their API is documented pretty well here. In case you are curious, here are the ones that my example script is using to create things on the DM side of the house:

Create Route

POST https://api.dailymile.com/routes.json

Create or Update GPS Track for a Route

PUT https://api.dailymile.com/routes/id/track.json

NOTE: As of Mar 28, 2011, when you upload via the API, the track shows up as being uploaded from API 101. DM has acknowledged that this is a display issue on their side.

Create an Entry

POST https://api.dailymile.com/entries.json

In order to use all of these calls above, DM requires that you have an OAuth key. This is fairly simple for those of you who are familiar with generating your own web service and such. For the rest of you, if you just want to be able to run this script and don't want to mess around with any of that stuff, you can use my GC Export application which is already setup with DM.

To use it, simply click this link. Then click the Connect to Dailymile link and approve it. It will kick back what your OAuth key is. Simply copy and paste that into the script in the section here:

// Set your oauth key for dailymile here.
$oauth = 'You need to generate this for the DM API';

That will take care of the OAuth key for the dailymile side of things.

The Full Script

#!/usr/bin/php
<?php

// Set your username and password for Garmin Connect here.
$username = 'username';
$password = 'password';

// Set your oauth key for dailymile here.
$oauth = 'You need to generate this for the DM API';

// Set this if you need it on your installation.
date_default_timezone_set('America/Chicago');

// End of user edits.

// URLs for various services
$urlGCLogin    = 'http://connect.garmin.com/signin';
$urlGCSearch   = 'http://connect.garmin.com/proxy/activity-search-service-1.0/json/activities?';
$urlGCActivity = 'http://connect.garmin.com/proxy/activity-service-1.1/gpx/activity/';
$urlDMPut      = 'https://api.dailymile.com/';
$urlDMGet      = 'http://api.dailymile.com/';

// Initially, we need to get a valid session cookie, so we pull the login page.
curl( $urlGCLogin );

// Now we'll actually login
curl( $urlGCLogin . '?login=login&login:signInButton=Sign%20In&javax.faces.ViewState=j_id1&login:loginUsernameField='.$username.'&login:password='.$password.'&login:rememberMe=on');


// Now we search GC for the latest activity.
// We support calling multiples from command line if specified,
// otherwise, only pull the last activity.
if ( ! empty( $argc ) && ( is_numeric( $argv[1] ) ) ) {
	$search_opts = array(
		'start' => 0,
		'limit' => $argv[1]
		);
} else {
	$search_opts = array(
		'start' => 0,
		'limit' => 1
		);
}

$result = curl( $urlGCSearch . http_build_query( $search_opts ) );
$json = json_decode( $result );

if ( ! $json ) {
	echo "Error: ";	
	switch(json_last_error()) {
		case JSON_ERROR_DEPTH:
			echo ' - Maximum stack depth exceeded';
			break;
		case JSON_ERROR_CTRL_CHAR:
			echo ' - Unexpected control character found';
			break;
		case JSON_ERROR_SYNTAX:
			echo ' - Syntax error, malformed JSON';
			break;
	}
	echo PHP_EOL;
	var_dump( $result );
	die();
}

// Info on the search, for future paging.
// @TODO: Add in support for loading all activities
$search     = $json->{'results'}->{'search'};

// Pull out just the list of activites
$activities = $json->{'results'}->{'activities'};

// Process each activity.
foreach ( $activities as $a ) {
	// Display which entry we're working on.
	print "Garmin Connect activity: [" . $a->{'activity'}->{'activityId'} . "] ";
	print $a->{'activity'}->{'beginTimestamp'}->{'display'}  . ": ";
	print $a->{'activity'}->{'activityName'}->{'value'} . "\n";

	// GC activity URL - to append to DM message
	$activity_gc_url = 'http://connect.garmin.com/activity/' . $a->{'activity'}->{'activityId'};

	// Change the activityType into something that DM understands
	switch( $a->{'activity'}->{'activityType'}->{'key'} ) {
		case 'running':
		case 'street_running':
		case 'track_running':
		case 'trail_running':
		case 'treadmill_running':
			$activity_type = 'running';
			break;

		case 'cycling':
		case 'cyclocross':
		case 'downhill_biking':
		case 'indoor_cycling':
		case 'mountain_biking':
		case 'recumbent_cycling':
		case 'road_biking':
		case 'track_cycling':
			$activity_type = 'cycling';
			break;

		case 'swimming':
		case 'lap_swimming':
		case 'open_water_swimming':
			$activity_type = 'swimming';
			break;

		case 'walking':
		case 'casual_walking':
		case 'speed_walking':
		case 'snow_shoe':
		case 'hiking':
			$activity_type = 'walking';
			break;

		default:
			$activity_type = 'fitness';
			break;
	}

	// Generate the DM Entry Name
	if ( $a->{'activity'}->{'activityName'}->{'value'} && $a->{'activity'}->{'activityName'}->{'value'} != 'Untitled' ) {
		$activity_name = $a->{'activity'}->{'activityName'}->{'value'} . ' (' . $a->{'activity'}->{'activityId'} . ')';
	} else {
		$activity_name = $a->{'activity'}->{'activityId'};
	}

	// Start building our DM entry array.
	$dm_entry = array();
	// Add in our Auth Token as it needs to be part of the post fields.
	$dm_entry{'oauth_token'} = $oauth;

	// Add message
	if ( $a->{'activity'}->{'activityDescription'}->{'value'} ) {
		$dm_entry{'message'} = $a->{'activity'}->{'activityDescription'}->{'value'};
		$dm_entry{'message'} .= "\nOriginal activity at: " . $activity_gc_url;
	} else {
		$dm_entry{'message'} = "\nOriginal activity at: " . $activity_gc_url;
	}

	// add geolocation:
	if ( $a->{'activity'}->{'beginLatitude'}->{'value'} && $a->{'activity'}->{'beginLongitude'}->{'value'} ) {
		$dm_entry{'lat'} = $a->{'activity'}->{'beginLatitude'}->{'value'};
		$dm_entry{'lon'} = $a->{'activity'}->{'beginLongitude'}->{'value'};
	}

	$dm_entry{'workout[activity_type]'} = $activity_type;
	$dm_entry{'workout[completed_at]'} = date( "c", strtotime( $a->{'activity'}->{'endTimestamp'}->{'display'} ) );
	$dm_entry{'workout[distance][value]'} = $a->{'activity'}->{'sumDistance'}->{'display'};
	$dm_entry{'workout[distance][units]'} = $a->{'activity'}->{'sumDistance'}->{'uom'};
	$dm_entry{'workout[duration]'} = $a->{'activity'}->{'sumElapsedDuration'}->{'value'};
	//$dm_entry{'workout[felt]'} = '';
	$dm_entry{'workout[calories]'} = $a->{'activity'}->{'sumEnergy'}->{'display'};
	$dm_entry{'workout[title]'} = $activity_name;

	// Download the GPX file from GC.
	print "\tDownloading .GPX ... ";
	$gpx_filename = './activities/activity_' . $a->{'activity'}->{'activityId'} . '.gpx';
	$save_file = fopen( $gpx_filename, 'w+' );
	$curl_opts = array(
		CURLOPT_FILE => $save_file
		);
	curl( $urlGCActivity . $a->{'activity'}->{'activityId'} . '?full=true', array(), array(), $curl_opts );
	fclose( $save_file );

	// Now we need to validate the .GPX.  If we have an activity without GPS data, GC still kicks out a .GPX file for it.
	// As I ride a trainer in the bad months, this is a common occurance for me, as I would imagine it would be for anyone
	// using a treadmill as well.
	$gpx = simplexml_load_file( $gpx_filename, 'SimpleXMLElement', LIBXML_NOCDATA );
	$gpxupload = ( count( $gpx->trk->trkseg->trkpt ) > 0);

	if ( $gpxupload ) {
		print "Done. GPX will be uploaded.\n";

		// Now we need to create a track on DM
		print "\tCreating dailymile track\n";
		$curl_post = array(
			'oauth_token' => $oauth,
			'name' => $activity_name,
			'activity_type' => $activity_type
			);
		$result = curl( $urlDMPut . 'routes.json', $curl_post );
		$json = json_decode( $result );

		// Get the route ID
		$dm_route_id = $json->{'id'};
		$dm_entry{'workout[route_id]'} = $dm_route_id;
		print "\t\tcreated route $dm_route_id\n";

		// Now we need to upload our .GPX file
		$put_file = fopen( $gpx_filename, 'r' );
		$curl_opts = array( 
			CURLOPT_PUT => 1,
			CURLOPT_INFILE => $put_file,
			CURLOPT_INFILESIZE => filesize( $gpx_filename )
			);

		$curl_header = array(
			'Content-Type' => 'application/gpx+xml'
			);

		print "\t\tuploading $gpx_filename as gpx track\n";
		curl( $urlDMPut . 'routes/' . $dm_route_id . '/track.json?oauth_token=' . $oauth, array(), $curl_header, $curl_opts );
		fclose( $put_file );
		print "\t\tfinished updating gpx track\n";
	} else {
		// We don't need to create a track, as we have no GPS track data. :(
		print "Done. No track points found.\n";
	}

	print "\tCreating dailymile workout entry\n";
	$result = curl( $urlDMPut . 'entries.json', $dm_entry );
	$json = json_decode( $result );

	print "\t\tcreated workout id " . $json->{'id'} . " (" . $json->{'url'} . ")\n";
}

print "\n\n";
// End

function curl( $url, $post = array(), $head = array(), $opts = array() )
{
	$cookie_file = '/tmp/cookies.txt';
	$ch = curl_init();

	//curl_setopt( $ch, CURLOPT_VERBOSE, 1 );
	curl_setopt( $ch, CURLOPT_URL, $url );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );	
	curl_setopt( $ch, CURLOPT_ENCODING, "gzip" );
	curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookie_file );
	curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookie_file );
	curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );

	foreach ( $opts as $k => $v ) {
		curl_setopt( $ch, $k, $v );
	}

	if ( count( $post ) > 0 ) {
		// POST mode
		curl_setopt( $ch, CURLOPT_POST, 1 );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, $post );
	}
	else {
		curl_setopt( $ch, CURLOPT_HTTPHEADER, $head );
		curl_setopt( $ch, CURLOPT_CRLF, 1 );
	}

	$success = curl_exec( $ch );

	if ( curl_errno( $ch ) !== 0 ) {
		throw new Exception( sprintf( '%s: CURL Error %d: %s', __CLASS__, curl_errno( $ch ), curl_error( $ch ) ) );
	}

	if ( curl_getinfo( $ch, CURLINFO_HTTP_CODE ) !== 200 ) {
		if ( curl_getinfo( $ch, CURLINFO_HTTP_CODE ) !== 201 ) {
			throw new Exception( sprintf( 'Bad return code(%1$d) for: %2$s', curl_getinfo( $ch, CURLINFO_HTTP_CODE ), $url ) );
		}
	}

	curl_close( $ch );
	return $success;
}

?>

That's it. It's pretty simple to do and runs well. You could launch a trigger based off of your GC RSS feed, so that once it picks up a new activity there, then it can pull that activity ID and post it out to DM for you automagically. Good luck, hope this helps a lot of you.

If you end up doing anything interesting with it, drop me a note at rmullins @ domain here. (Damned spam bots).

 

Last updated: March 28 2011 13:37:54.