The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package GPS::Garmin::Connect;

use warnings;
use strict;


use LWP::UserAgent;
use HTML::Form;
use JSON;
use Error;


=head1 NAME

GPS::Garmin::Connect - Allows simple fetching of 
activities from http://connect.garmin.com

=head1 VERSION

Version 0.01

=cut

our $VERSION = '0.01';


=head1 SYNOPSIS

This module is a simple helper to fetch and parse activities
from http://connect.garmin.com



    use GPS::Garmin::Connect;

    my $connect = GPS::Garmin::Connect->new();
    my $json = $connect->fetchdata( $username, $password );

    my $activities = $connect->parse( $json );

    foreach my $activity (@$activities) {
        print "My activity $activity->{activity} - HR: $activity->{heartrate}\n";
    }


=head1 FUNCTIONS

=head2 new

=cut

sub new {
  my $self = shift;
  return bless {
                _loginurl => 'http://connect.garmin.com/signin',
               }, $self;
}

=head2 fetchdata

    $connect->fetchdata( $username, $password );

Logins into connect.garmin.com and fetches all activities and returns 
a JSON string which can be parsed using L<parse>.

=cut

sub fetchdata {
  my ($pkg, $username, $password) = @_;
  my $loginurl = $pkg->{_loginurl};
  my $ua = LWP::UserAgent->new();
  $ua->cookie_jar( { } );
  push @{ $ua->requests_redirectable }, 'POST';



  # Fetch login form to get a session id ..
  my $loginformreq = HTTP::Request->new(GET => $loginurl);
  my $loginformres = $ua->request($loginformreq);

  throw Error::Simple('Error while requesting login form: ' . $loginformres->status_line)
    unless $loginformres->is_success;
  
  my $loginform = HTML::Form->parse($loginformres->content, $loginurl);
  $loginform->value( 'login:loginUsernameField' => $username );
  $loginform->value( 'login:password' => $password );
  
  # Send login request ..
  my $loginres = $ua->request($loginform->click);
  
  if ($loginres->is_success) {
    # we successfully logged in (probably)
  } else {
    throw Error::Simple("Error while trying to log in: ".$loginres->status_line);
  }

  # We can now retrieve our activity ...
  my $req = HTTP::Request->new(GET => 'http://connect.garmin.com/proxy/activity-search-service-1.0/json/activities?_dc=1220170621856&start=0&limit=50');

  my $res = $ua->request($req);

  if ($res->is_success) {
    return $res->content;
  } else {
    throw Error::Simple("error while requesting activities (".$res->status_line.")");
  }
}

=head2 parse

method responsible for parsing the json data and returning a simplified array ref of hash refs:

    $VAR1 = [
         {
            'begindate' => '2009-02-17',
            'distance' => 3156,
            'name' => 'Untitled',
            'heartrate' => 162,
            'duration' => 1980,
            'activity' => 'Untitled',
            'activityid' => '2194739',
            'id' => '2194739',
            'type' => 'Uncategorized',
            'begin' => 'Tue, Feb 17 \'09 08:27 AM'
          },
         {
            'begindate' => '2009-02-17',
            'distance' => 2200,
            'name' => 'Untitled',
            'heartrate' => 157,
            'duration' => 1500,
            'activity' => 'Untitled',
            'activityid' => '2194738',
            'id' => '2194738',
            'type' => 'Uncategorized',
            'begin' => 'Tue, Feb 17 \'09 08:02 AM'
          },


=cut

sub parse {
  my ($pkg, $content) = @_;
  my $json = JSON->new();

  my $results = $json->decode($content);


  my $activities = $results->{results}->{activities};


  my $simpleactivities = [];
  foreach my $activity (@$activities) {
    my $a = $activity->{activity};
    # convert it to something more "userfriendly"
    my $hr = undef;
    if ($a->{weightedMeanHeartRate}) {
      $hr = int($a->{weightedMeanHeartRate}->{value});
    }
    my $durstr = $a->{sumDuration}->{display};
    my ($hrs, $min, $sec) = split(/:/,$durstr);
    my $duration = (($hrs * 60) + $min) * 60 + $sec;
    my $ac = { 'id' => $a->{activityId},
               'activityid' => $a->{activityId},
               'activity' => $a->{activityName}->{value},
               'name' => $a->{activityName}->{value},
               'type' => $a->{activityType}->{display},
               'distance' => int($a->{sumDistance}->{value}*1000),#sprintf("%.2f",$a->{sumDistance}->{value}),
               'duration' => $duration,
               'begin' => $a->{beginTimestamp}->{display},
               'begindate' => $a->{beginTimestamp}->{value},
               'heartrate' => $hr,
             };
    
    push(@$simpleactivities, $ac);
  }
  return $simpleactivities;
}



=head1 AUTHOR

Herbert Poul, C<< <hpoul at cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-gps-garmin-connect at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=GPS-Garmin-Connect>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.




=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc GPS::Garmin::Connect


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=GPS-Garmin-Connect>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/GPS-Garmin-Connect>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/GPS-Garmin-Connect>

=item * Search CPAN

L<http://search.cpan.org/dist/GPS-Garmin-Connect>

=back


=head1 ACKNOWLEDGEMENTS


=head1 COPYRIGHT & LICENSE

Copyright 2009 Herbert Poul, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.


=cut

1; # End of GPS::Garmin::Connect