File Coverage

blib/lib/WebService/GarminConnect.pm
Criterion Covered Total %
statement 34 122 27.8
branch 2 40 5.0
condition 1 5 20.0
subroutine 11 14 78.5
pod 2 2 100.0
total 50 183 27.3


line stmt bran cond sub pod time code
1             package WebService::GarminConnect;
2              
3 4     4   294178 use 5.006;
  4         49  
4 4     4   27 use warnings FATAL => 'all';
  4         7  
  4         141  
5 4     4   21 use strict;
  4         8  
  4         71  
6 4     4   35 use Carp;
  4         19  
  4         254  
7 4     4   3085 use LWP::UserAgent;
  4         230411  
  4         147  
8 4     4   35 use URI;
  4         11  
  4         118  
9 4     4   2702 use JSON;
  4         43777  
  4         23  
10 4     4   1989 use Data::Dumper;
  4         13353  
  4         239  
11 4     4   2081 use WWW::OAuth;
  4         86377  
  4         152  
12 4     4   38 use WWW::OAuth::Util qw( form_urldecode );
  4         10  
  4         4906  
13             #use LWP::ConsoleLogger::Everywhere ();
14              
15             our $VERSION = '1.1.0'; # VERSION
16              
17             =head1 NAME
18              
19             WebService::GarminConnect - Access data from Garmin Connect
20              
21             =head1 VERSION
22              
23             version 1.1.0
24              
25             =head1 SYNOPSIS
26              
27             With WebService::GarminConnect, you can search the activities stored on the
28             Garmin Connect site.
29              
30             use WebService::GarminConnect;
31              
32             my $gc = WebService::GarminConnect->new( username => 'myuser',
33             password => 'password' );
34             my @activities = $gc->activities( limit => 20 );
35             foreach my $a ( @activities ) {
36             my $name = $a->{name};
37             ...
38             }
39              
40             =head1 FUNCTIONS
41              
42             =head2 new( %options )
43              
44             Creates a new WebService::GarminConnect object. One or more options may be
45             specified:
46              
47             =over
48              
49             =item username
50              
51             (Required) The Garmin Connect username to use for searches.
52              
53             =item password
54              
55             (Required) The user's Garmin Connect password.
56              
57             =item searchurl
58              
59             (Optional) Override the default search URL for Garmin Connect.
60              
61             =back
62              
63             =cut
64              
65             sub new {
66 4     4 1 2033 my $self = shift;
67 4         13 my %options = @_;
68              
69             # Check for mandatory options
70 4         17 foreach my $required_option ( qw( username password ) ) {
71             croak "option \"$required_option\" is required"
72 6 100       337 unless defined $options{$required_option};
73             }
74              
75             return bless {
76             username => $options{username},
77             password => $options{password},
78 1   50     13 searchurl => $options{searchurl} || 'https://connectapi.garmin.com/activitylist-service/activities/search/activities',
79             }, $self;
80             }
81              
82             sub _login {
83 0     0     my $self = shift;
84              
85             # Bail out if we're already logged in.
86 0 0         return if defined $self->{is_logged_in};
87              
88 0           my $ua = LWP::UserAgent->new(agent => 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) ' .
89             'AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148');
90 0           $ua->cookie_jar( {} );
91 0           push @{ $ua->requests_redirectable }, 'POST';
  0            
92              
93 0           my %sso_embed_params = (
94             id => 'gauth-widget',
95             embedWidget => 'true',
96             gauthHost => 'https://sso.garmin.com/sso',
97             );
98 0           my $uri = URI->new('https://sso.garmin.com/sso/embed');
99 0           $uri->query_form(%sso_embed_params);
100 0           my $response = $ua->get($uri);
101 0 0         croak "Can't retrieve /sso/embed: " . $response->status_line
102             unless $response->is_success;
103              
104 0           my %signin_params = (
105             id => 'gauth-widget',
106             embedWidget => 'true',
107             gauthHost => 'https://sso.garmin.com/sso/embed',
108             service => 'https://sso.garmin.com/sso/embed',
109             source => 'https://sso.garmin.com/sso/embed',
110             redirectAfterAccountLoginUrl => 'https://sso.garmin.com/sso/embed',
111             redirectAfterAccountCreationUrl => 'https://sso.garmin.com/sso/embed',
112             );
113 0           $uri = URI->new('https://sso.garmin.com/sso/signin');
114 0           $uri->query_form(%signin_params);
115 0           $response = $ua->get($uri);
116 0 0         croak "Can't retrieve /sso/signin: " . $response->status_line
117             unless $response->is_success;
118             # get the CSRF token from the response, it's a hidden form field
119 0           my $csrf_token;
120 0 0         if ($response->decoded_content =~ /name="_csrf"\s+value="(.+)"/) {
121 0           $csrf_token = $1;
122             } else {
123 0           croak "couldn't find CSRF token";
124             }
125              
126             # submit login form with email and password
127             $response = $ua->post($uri, Referer => "$uri", Content => {
128             username => $self->{username},
129             password => $self->{password},
130 0           embed => 'true',
131             _csrf => $csrf_token,
132             });
133 0 0         croak "Can't submit login page: " . $response->status_line
134             unless $response->is_success;
135 0           my $title;
136 0 0         if ($response->decoded_content =~ m:(.+):) {
137 0           $title = $1;
138             } else {
139 0           croak "couldn't find in login response"; </td> </tr> <tr> <td class="h" > <a name="140">140</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="141">141</a> </td> <td class="c0" > <a href="#142"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#141-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if ($title ne 'Success') { </td> </tr> <tr> <td class="h" > <a name="142">142</a> </td> <td class="c0" > <a href="#144"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> croak "expected post-login <title> of \"Success\", not \"$title\""; </td> </tr> <tr> <td class="h" > <a name="143">143</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="144">144</a> </td> <td class="c0" > <a href="#145"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $ticket; </td> </tr> <tr> <td class="h" > <a name="145">145</a> </td> <td class="c0" > <a href="#146"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#145-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if ($response->decoded_content =~ /embed\?ticket=([^"]+)"/) { </td> </tr> <tr> <td class="h" > <a name="146">146</a> </td> <td class="c0" > <a href="#148"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $ticket = $1; </td> </tr> <tr> <td class="h" > <a name="147">147</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } else { </td> </tr> <tr> <td class="h" > <a name="148">148</a> </td> <td class="c0" > <a href="#154"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> croak "couldn't find ticket in login response"; </td> </tr> <tr> <td class="h" > <a name="149">149</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="150">150</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="151">151</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # get oauth1 token, these came from https://thegarth.s3.amazonaws.com/oauth_consumer.json </td> </tr> <tr> <td class="h" > <a name="152">152</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # and are what the Garmin Connect mobile app uses. Perhaps we should </td> </tr> <tr> <td class="h" > <a name="153">153</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # try to fetch these from there at runtime in case they ever change? </td> </tr> <tr> <td class="h" > <a name="154">154</a> </td> <td class="c0" > <a href="#160"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $oauth = WWW::OAuth->new( </td> </tr> <tr> <td class="h" > <a name="155">155</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> client_id => "fc3e99d2-118c-44b8-8ae3-03370dde24c0", </td> </tr> <tr> <td class="h" > <a name="156">156</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> client_secret => "E08WAR897WEy2knn7aFBrvegVAf0AFdWBBF", </td> </tr> <tr> <td class="h" > <a name="157">157</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ); </td> </tr> <tr> <td class="h" > <a name="158">158</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="159">159</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="160">160</a> </td> <td class="c0" > <a href="#163"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $uri = 'https://connectapi.garmin.com/oauth-service/oauth/' . </td> </tr> <tr> <td class="h" > <a name="161">161</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> "preauthorized?ticket=$ticket&login-url=" . </td> </tr> <tr> <td class="h" > <a name="162">162</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> 'https://sso.garmin.com/sso/embed&accepts-mfa-tokens=true'; </td> </tr> <tr> <td class="h" > <a name="163">163</a> </td> <td class="c0" > <a href="# "> 0 </a> </td> <td >   </td> <td >   </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--subroutine.html#163-1"> 0 </a> </td> <td >   </td> <td >   </td> <td class="s"> $ua->add_handler(request_prepare => sub { $oauth->authenticate($_[0]) }); </td> </tr> <tr> <td class="h" > <a > </a> </td> <td class="c0" > <a href="#164"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="164">164</a> </td> <td class="c0" > <a href="#165"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $response = $ua->get($uri); </td> </tr> <tr> <td class="h" > <a name="165">165</a> </td> <td class="c0" > <a href="#167"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#165-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> croak "Can't retrieve oauth1 page: " . $response->status_line </td> </tr> <tr> <td class="h" > <a name="166">166</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> unless $response->is_success; </td> </tr> <tr> <td class="h" > <a name="167">167</a> </td> <td class="c0" > <a href="# "> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my %response_data = @{form_urldecode($response->content)}; </td> </tr> <tr> <td class="h" > <a > </a> </td> <td class="c0" > <a href="#168"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="168">168</a> </td> <td class="c0" > <a href="#169"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> foreach my $key ( qw( oauth_token oauth_token_secret ) ) { </td> </tr> <tr> <td class="h" > <a name="169">169</a> </td> <td class="c0" > <a href="#170"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#169-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if (!defined $response_data{$key}) { </td> </tr> <tr> <td class="h" > <a name="170">170</a> </td> <td class="c0" > <a href="#173"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> croak "oauth response didn't include \"$key\""; </td> </tr> <tr> <td class="h" > <a name="171">171</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="172">172</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="173">173</a> </td> <td class="c0" > <a href="#174"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $oauth->token($response_data{oauth_token}); </td> </tr> <tr> <td class="h" > <a name="174">174</a> </td> <td class="c0" > <a href="#176"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $oauth->token_secret($response_data{oauth_token_secret}); </td> </tr> <tr> <td class="h" > <a name="175">175</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="176">176</a> </td> <td class="c0" > <a href="#177"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $uri = 'https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0'; </td> </tr> <tr> <td class="h" > <a name="177">177</a> </td> <td class="c0" > <a href="#178"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $response = $ua->post($uri); </td> </tr> <tr> <td class="h" > <a name="178">178</a> </td> <td class="c0" > <a href="#180"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#178-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> croak "Can't retrieve oauth1 page: " . $response->status_line </td> </tr> <tr> <td class="h" > <a name="179">179</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> unless $response->is_success; </td> </tr> <tr> <td class="h" > <a name="180">180</a> </td> <td class="c0" > <a href="#181"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $response_data = decode_json($response->content); </td> </tr> <tr> <td class="h" > <a name="181">181</a> </td> <td class="c0" > <a href="#182"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#181-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if (!defined $response_data->{access_token}) { </td> </tr> <tr> <td class="h" > <a name="182">182</a> </td> <td class="c0" > <a href="#186"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> croak "couldn't find access token in response"; </td> </tr> <tr> <td class="h" > <a name="183">183</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="184">184</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="185">185</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # make subsequent calls use the access token in the Authorization header </td> </tr> <tr> <td class="h" > <a name="186">186</a> </td> <td class="c0" > <a href="#187"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $ua->remove_handler('request_prepare'); </td> </tr> <tr> <td class="h" > <a name="187">187</a> </td> <td class="c0" > <a href="#195"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $ua->default_header('Authorization', 'Bearer ' . $response_data->{access_token}); </td> </tr> <tr> <td class="h" > <a name="188">188</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="189">189</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> #$uri = 'https://connectapi.garmin.com/activitylist-service/activities/search/activities?limit=20&start=0'; </td> </tr> <tr> <td class="h" > <a name="190">190</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> #$response = $ua->get($uri); </td> </tr> <tr> <td class="h" > <a name="191">191</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> #croak "Can't retrieve activity search page: " . $response->status_line </td> </tr> <tr> <td class="h" > <a name="192">192</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # unless $response->is_success; </td> </tr> <tr> <td class="h" > <a name="193">193</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="194">194</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # Record our logged-in status so future calls will skip login. </td> </tr> <tr> <td class="h" > <a name="195">195</a> </td> <td class="c0" > <a href="#196"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $self->{useragent} = $ua; </td> </tr> <tr> <td class="h" > <a name="196">196</a> </td> <td class="c0" > <a href="#222"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $self->{is_logged_in} = 1; </td> </tr> <tr> <td class="h" > <a name="197">197</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="198">198</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="199">199</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head2 activities( %search_criteria ) </td> </tr> <tr> <td class="h" > <a name="200">200</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="201">201</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Returns a list of activities matching the requested criteria. If no criteria </td> </tr> <tr> <td class="h" > <a name="202">202</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> are specified, returns all the user's activities. Possible criteria: </td> </tr> <tr> <td class="h" > <a name="203">203</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="204">204</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over </td> </tr> <tr> <td class="h" > <a name="205">205</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="206">206</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item limit </td> </tr> <tr> <td class="h" > <a name="207">207</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="208">208</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> (Optional) The maximum number of activities to return. If not specified, </td> </tr> <tr> <td class="h" > <a name="209">209</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> all the user's activities will be returned. </td> </tr> <tr> <td class="h" > <a name="210">210</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="211">211</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item pagesize </td> </tr> <tr> <td class="h" > <a name="212">212</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="213">213</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> (Optional) The number of activities to return in each call to Garmin </td> </tr> <tr> <td class="h" > <a name="214">214</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Connect. (One call to this subroutine may call Garmin Connect several </td> </tr> <tr> <td class="h" > <a name="215">215</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> times to retrieve all the requested activities.) Defaults to 50. </td> </tr> <tr> <td class="h" > <a name="216">216</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="217">217</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="218">218</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="219">219</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =cut </td> </tr> <tr> <td class="h" > <a name="220">220</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="221">221</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> sub activities { </td> </tr> <tr> <td class="h" > <a name="222">222</a> </td> <td class="c0" > <a href="#223"> 0 </a> </td> <td >   </td> <td >   </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--subroutine.html#222-1"> 0 </a> </td> <td class="c3" > <a href="blib-lib-WebService-GarminConnect-pm--subroutine.html#222-1"> 1 </a> </td> <td >   </td> <td class="s"> my $self = shift; </td> </tr> <tr> <td class="h" > <a name="223">223</a> </td> <td class="c0" > <a href="#224"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my %opts = @_; </td> </tr> <tr> <td class="h" > <a name="224">224</a> </td> <td class="c0" > <a href="#227"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $json = JSON->new(); </td> </tr> <tr> <td class="h" > <a name="225">225</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="226">226</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # Ensure we are logged in </td> </tr> <tr> <td class="h" > <a name="227">227</a> </td> <td class="c0" > <a href="#228"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $self->_login(); </td> </tr> <tr> <td class="h" > <a name="228">228</a> </td> <td class="c0" > <a href="#231"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $ua = $self->{useragent}; </td> </tr> <tr> <td class="h" > <a name="229">229</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="230">230</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # We can only fetch a fixed number of activities at a time. </td> </tr> <tr> <td class="h" > <a name="231">231</a> </td> <td class="c0" > <a href="#232"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my @activities; </td> </tr> <tr> <td class="h" > <a name="232">232</a> </td> <td class="c0" > <a href="#233"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $start = 0; </td> </tr> <tr> <td class="h" > <a name="233">233</a> </td> <td class="c0" > <a href="#234"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $pagesize = 50; </td> </tr> <tr> <td class="h" > <a name="234">234</a> </td> <td class="c0" > <a href="#235"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#234-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if( defined $opts{pagesize} ) { </td> </tr> <tr> <td class="h" > <a name="235">235</a> </td> <td class="c0" > <a href="#236"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#235-1"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--condition.html#235-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if( $opts{pagesize} > 0 && $opts{pagesize} < 50 ) { </td> </tr> <tr> <td class="h" > <a name="236">236</a> </td> <td class="c0" > <a href="#241"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $pagesize = $opts{pagesize}; </td> </tr> <tr> <td class="h" > <a name="237">237</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="238">238</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="239">239</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="240">240</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # Special case when the limit is smaller than one page. </td> </tr> <tr> <td class="h" > <a name="241">241</a> </td> <td class="c0" > <a href="#242"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#241-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if( defined $opts{limit} ) { </td> </tr> <tr> <td class="h" > <a name="242">242</a> </td> <td class="c0" > <a href="#243"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#242-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if( $opts{limit} < $pagesize ) { </td> </tr> <tr> <td class="h" > <a name="243">243</a> </td> <td class="c0" > <a href="#247"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $pagesize = $opts{limit}; </td> </tr> <tr> <td class="h" > <a name="244">244</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="245">245</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="246">246</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="247">247</a> </td> <td class="c0" > <a href="#251"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $data = []; </td> </tr> <tr> <td class="h" > <a name="248">248</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> do { </td> </tr> <tr> <td class="h" > <a name="249">249</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # Make a search request </td> </tr> <tr> <td class="h" > <a name="250">250</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $searchurl = $self->{searchurl} . </td> </tr> <tr> <td class="h" > <a name="251">251</a> </td> <td class="c0" > <a href="#253"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> "?start=$start&limit=$pagesize"; </td> </tr> <tr> <td class="h" > <a name="252">252</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="253">253</a> </td> <td class="c0" > <a href="#259"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $headers = [ </td> </tr> <tr> <td class="h" > <a name="254">254</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> 'NK' => 'NT', </td> </tr> <tr> <td class="h" > <a name="255">255</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> 'X-app-ver' => '4.71.1.4', </td> </tr> <tr> <td class="h" > <a name="256">256</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> 'X-lang' => 'en-US', </td> </tr> <tr> <td class="h" > <a name="257">257</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> 'X-Requested-With' => 'XMLHttpRequest', </td> </tr> <tr> <td class="h" > <a name="258">258</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ]; </td> </tr> <tr> <td class="h" > <a name="259">259</a> </td> <td class="c0" > <a href="#260"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $request = HTTP::Request->new('GET', $searchurl, $headers); </td> </tr> <tr> <td class="h" > <a name="260">260</a> </td> <td class="c0" > <a href="#261"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $response = $ua->request($request); </td> </tr> <tr> <td class="h" > <a name="261">261</a> </td> <td class="c0" > <a href="#265"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#261-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> croak "Can't make search request: " . $response->status_line </td> </tr> <tr> <td class="h" > <a name="262">262</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> unless $response->is_success; </td> </tr> <tr> <td class="h" > <a name="263">263</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="264">264</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # Parse the JSON search results </td> </tr> <tr> <td class="h" > <a name="265">265</a> </td> <td class="c0" > <a href="#268"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $data = $json->decode($response->content); </td> </tr> <tr> <td class="h" > <a name="266">266</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="267">267</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # Add this set of activities to the list. </td> </tr> <tr> <td class="h" > <a name="268">268</a> </td> <td class="c0" > <a href="# "> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> foreach my $activity ( @{$data} ) { </td> </tr> <tr> <td class="h" > <a > </a> </td> <td class="c0" > <a href="#269"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="269">269</a> </td> <td class="c0" > <a href="#271"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#269-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if( defined $opts{limit} ) { </td> </tr> <tr> <td class="h" > <a name="270">270</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # add this activity only if we're under the limit </td> </tr> <tr> <td class="h" > <a name="271">271</a> </td> <td class="c0" > <a href="#272"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#271-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if( @activities < $opts{limit} ) { </td> </tr> <tr> <td class="h" > <a name="272">272</a> </td> <td class="c0" > <a href="#274"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> push @activities, { activity => $activity }; </td> </tr> <tr> <td class="h" > <a name="273">273</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } else { </td> </tr> <tr> <td class="h" > <a name="274">274</a> </td> <td class="c0" > <a href="#275"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $data = []; # stop retrieving more activities </td> </tr> <tr> <td class="h" > <a name="275">275</a> </td> <td class="c0" > <a href="#278"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> last; </td> </tr> <tr> <td class="h" > <a name="276">276</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="277">277</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } else { </td> </tr> <tr> <td class="h" > <a name="278">278</a> </td> <td class="c0" > <a href="#283"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> push @activities, { activity => $activity }; </td> </tr> <tr> <td class="h" > <a name="279">279</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="280">280</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="281">281</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="282">282</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # Increment the start offset for the next request. </td> </tr> <tr> <td class="h" > <a name="283">283</a> </td> <td class="c0" > <a href="#285"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $start += $pagesize; </td> </tr> <tr> <td class="h" > <a name="284">284</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="285">285</a> </td> <td class="c0" > <a href="# "> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } while( @{$data} > 0 ); </td> </tr> <tr> <td class="h" > <a > </a> </td> <td class="c0" > <a href="#287"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="286">286</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="287">287</a> </td> <td class="c0" > 0 </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> return @activities; </td> </tr> <tr> <td class="h" > <a name="288">288</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="289">289</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="290">290</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 AUTHOR </td> </tr> <tr> <td class="h" > <a name="291">291</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="292">292</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Joel Loudermilk, C<< <joel at loudermilk.org> >> </td> </tr> <tr> <td class="h" > <a name="293">293</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="294">294</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 BUGS </td> </tr> <tr> <td class="h" > <a name="295">295</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="296">296</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Please report any bugs or feature requests to L<https://github.com/jlouder/garmin-connect-perl/issues>. </td> </tr> <tr> <td class="h" > <a name="297">297</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="298">298</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="299">299</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="300">300</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="301">301</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 SUPPORT </td> </tr> <tr> <td class="h" > <a name="302">302</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="303">303</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> You can find documentation for this module with the perldoc command. </td> </tr> <tr> <td class="h" > <a name="304">304</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="305">305</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> perldoc WebService::GarminConnect </td> </tr> <tr> <td class="h" > <a name="306">306</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="307">307</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="308">308</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> You can also look for information at: </td> </tr> <tr> <td class="h" > <a name="309">309</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="310">310</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="311">311</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="312">312</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item * AnnoCPAN: Annotated CPAN documentation </td> </tr> <tr> <td class="h" > <a name="313">313</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="314">314</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> L<http://annocpan.org/dist/WebService-GarminConnect> </td> </tr> <tr> <td class="h" > <a name="315">315</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="316">316</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item * CPAN Ratings </td> </tr> <tr> <td class="h" > <a name="317">317</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="318">318</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> L<http://cpanratings.perl.org/d/WebService-GarminConnect> </td> </tr> <tr> <td class="h" > <a name="319">319</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="320">320</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item * Search CPAN </td> </tr> <tr> <td class="h" > <a name="321">321</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="322">322</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> L<http://search.cpan.org/dist/WebService-GarminConnect> </td> </tr> <tr> <td class="h" > <a name="323">323</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="324">324</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item * GitHub Repository </td> </tr> <tr> <td class="h" > <a name="325">325</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="326">326</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> L<https://github.com/jlouder/garmin-connect-perl> </td> </tr> <tr> <td class="h" > <a name="327">327</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="328">328</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="329">329</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="330">330</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 COPYRIGHT & LICENSE </td> </tr> <tr> <td class="h" > <a name="331">331</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="332">332</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Copyright 2023 Joel Loudermilk. </td> </tr> <tr> <td class="h" > <a name="333">333</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="334">334</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> This program is free software: you can redistribute it and/or modify </td> </tr> <tr> <td class="h" > <a name="335">335</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> it under the terms of the GNU General Public License as published by </td> </tr> <tr> <td class="h" > <a name="336">336</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> the Free Software Foundation, either version 3 of the License, or </td> </tr> <tr> <td class="h" > <a name="337">337</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> (at your option) any later version. </td> </tr> <tr> <td class="h" > <a name="338">338</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="339">339</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> This program is distributed in the hope that it will be useful, </td> </tr> <tr> <td class="h" > <a name="340">340</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> but WITHOUT ANY WARRANTY; without even the implied warranty of </td> </tr> <tr> <td class="h" > <a name="341">341</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the </td> </tr> <tr> <td class="h" > <a name="342">342</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> GNU General Public License for more details. </td> </tr> <tr> <td class="h" > <a name="343">343</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="344">344</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> You should have received a copy of the GNU General Public License </td> </tr> <tr> <td class="h" > <a name="345">345</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> along with this program. If not, see L<http://www.gnu.org/licenses/>. </td> </tr> <tr> <td class="h" > <a name="346">346</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="347">347</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =cut </td> </tr> <tr> <td class="h" > <a name="348">348</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="349">349</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> 1; # End of WebService::GarminConnect </td> </tr> </table> </body> </html>