WebService-GarminConnect

 view release on metacpan or  search on metacpan

lib/WebService/GarminConnect.pm  view on Meta::CPAN


=head2 new( %options )

Creates a new WebService::GarminConnect object. One or more options may be
specified:

=over

=item username

(Required) The Garmin Connect username to use for searches.

=item password

(Required) The user's Garmin Connect password.

=item cache_dir

(Optional) Directory where the user's authentication token will be cached.
If not specified, defaults to $HOME/.cache/webservice-garminconnect.

=item searchurl

(Optional) Override the default search URL for Garmin Connect.

=back

=cut

sub new {
  my $self = shift;
  my %options = @_;

  # Check for mandatory options
  foreach my $required_option ( qw( username password ) ) {
    croak "option \"$required_option\" is required"
      unless defined $options{$required_option};
  }

  return bless {
    username  => $options{username},
    password  => $options{password},
    cache_dir => $options{cache_dir},
    searchurl => $options{searchurl} || 'https://connectapi.garmin.com/activitylist-service/activities/search/activities',
  }, $self;
}

sub _login {
  my $self = shift;

  # Bail out if we're already logged in.
  return if defined $self->{is_logged_in};

  my $ua = LWP::UserAgent->new(agent => 'GCM-iOS-5.7.2.1');
  $ua->cookie_jar( {} );
  push @{ $ua->requests_redirectable }, 'POST';

  # location for saved access token
  my $cache_path = $self->{cache_dir};
  if (!defined $cache_path) {
    $cache_path = (getpwuid($>))[7]."/.cache";
    -d $cache_path || mkdir $cache_path, 0700;
    $cache_path .= "/webservice-garminconnect";
    -d $cache_path || mkdir $cache_path, 0700;
  }
  # untaint
  $self->{username} =~ m/([a-z0-9+\-_.=\?@]+)/i;
  $cache_path .= "/${1}_oauth";

  # try saved access token
  if (open my $cache_fh, '<', $cache_path) {
    (my $access_token = <$cache_fh>) =~ s/\s+//;

    $ua->default_header('Authorization', 'Bearer ' . $access_token);
    $self->{useragent} = $ua;
    $self->{is_logged_in} = 1;

    # simple api call to validate
    eval { $self->profile };
    return unless $@;
  }

  my %sso_embed_params = (
    id          => 'gauth-widget',
    embedWidget => 'true',
    gauthHost   => 'https://sso.garmin.com/sso',
  );
  my $uri = URI->new('https://sso.garmin.com/sso/embed');
  $uri->query_form(%sso_embed_params);
  my $response = $ua->get($uri);
  croak "Can't retrieve /sso/embed: " . $response->status_line
    unless $response->is_success;

  my %signin_params = (
    id                              => 'gauth-widget',
    embedWidget                     => 'true',
    gauthHost                       => 'https://sso.garmin.com/sso/embed',
    service                         => 'https://sso.garmin.com/sso/embed',
    source                          => 'https://sso.garmin.com/sso/embed',
    redirectAfterAccountLoginUrl    => 'https://sso.garmin.com/sso/embed',
    redirectAfterAccountCreationUrl => 'https://sso.garmin.com/sso/embed',
  );
  $uri = URI->new('https://sso.garmin.com/sso/signin');
  $uri->query_form(%signin_params);
  $response = $ua->get($uri);
  croak "Can't retrieve /sso/signin: " . $response->status_line
    unless $response->is_success;
  # get the CSRF token from the response, it's a hidden form field
  my $csrf_token;
  if ($response->decoded_content =~ /name="_csrf"\s+value="(.+?)"/) {
    $csrf_token = $1;
  } else {
    croak "couldn't find CSRF token";
  }

  # submit login form with email and password
  $response = $ua->post($uri, Referer => "$uri", Content => {
    username => $self->{username},
    password => $self->{password},
    embed    => 'true',
    _csrf    => $csrf_token,



( run in 2.140 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )