File Coverage

blib/lib/WebService/GarminConnect.pm
Criterion Covered Total %
statement 34 153 22.2
branch 2 52 3.8
condition 1 5 20.0
subroutine 11 16 68.7
pod 3 3 100.0
total 51 229 22.2


line stmt bran cond sub pod time code
1             package WebService::GarminConnect;
2              
3 6     6   826560 use 5.006;
  6         20  
4 6     6   34 use warnings FATAL => 'all';
  6         8  
  6         394  
5 6     6   30 use strict;
  6         26  
  6         147  
6 6     6   24 use Carp;
  6         14  
  6         436  
7 6     6   4177 use LWP::UserAgent;
  6         392949  
  6         329  
8 6     6   64 use URI;
  6         17  
  6         174  
9 6     6   4534 use JSON;
  6         81881  
  6         58  
10 6     6   2194 use Data::Dumper;
  6         15572  
  6         529  
11 6     6   3373 use WWW::OAuth;
  6         165115  
  6         359  
12 6     6   56 use WWW::OAuth::Util qw( form_urldecode );
  6         13  
  6         11767  
13             #use LWP::ConsoleLogger::Everywhere ();
14              
15             our $VERSION = '1.1.2'; # VERSION
16              
17             =head1 NAME
18              
19             WebService::GarminConnect - Access data from Garmin Connect
20              
21             =head1 VERSION
22              
23             version 1.1.2
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 cache_dir
58              
59             (Optional) Directory where the user's authentication token will be cached.
60             If not specified, defaults to $HOME/.cache/webservice-garminconnect.
61              
62             =item searchurl
63              
64             (Optional) Override the default search URL for Garmin Connect.
65              
66             =back
67              
68             =cut
69              
70             sub new {
71 4     4 1 173958 my $self = shift;
72 4         18 my %options = @_;
73              
74             # Check for mandatory options
75 4         11 foreach my $required_option ( qw( username password ) ) {
76             croak "option \"$required_option\" is required"
77 6 100       441 unless defined $options{$required_option};
78             }
79              
80             return bless {
81             username => $options{username},
82             password => $options{password},
83             cache_dir => $options{cache_dir},
84 1   50     20 searchurl => $options{searchurl} || 'https://connectapi.garmin.com/activitylist-service/activities/search/activities',
85             }, $self;
86             }
87              
88             sub _login {
89 0     0     my $self = shift;
90              
91             # Bail out if we're already logged in.
92 0 0         return if defined $self->{is_logged_in};
93              
94 0           my $ua = LWP::UserAgent->new(agent => 'GCM-iOS-5.7.2.1');
95 0           $ua->cookie_jar( {} );
96 0           push @{ $ua->requests_redirectable }, 'POST';
  0            
97              
98             # location for saved access token
99 0           my $cache_path = $self->{cache_dir};
100 0 0         if (!defined $cache_path) {
101 0           $cache_path = (getpwuid($>))[7]."/.cache";
102 0 0         -d $cache_path || mkdir $cache_path, 0700;
103 0           $cache_path .= "/webservice-garminconnect";
104 0 0         -d $cache_path || mkdir $cache_path, 0700;
105             }
106             # untaint
107 0           $self->{username} =~ m/([a-z0-9+\-_.=\?@]+)/i;
108 0           $cache_path .= "/${1}_oauth";
109              
110             # try saved access token
111 0 0         if (open my $cache_fh, '<', $cache_path) {
112 0           (my $access_token = <$cache_fh>) =~ s/\s+//;
113              
114 0           $ua->default_header('Authorization', 'Bearer ' . $access_token);
115 0           $self->{useragent} = $ua;
116 0           $self->{is_logged_in} = 1;
117              
118             # simple api call to validate
119 0           eval { $self->profile };
  0            
120 0 0         return unless $@;
121             }
122              
123 0           my %sso_embed_params = (
124             id => 'gauth-widget',
125             embedWidget => 'true',
126             gauthHost => 'https://sso.garmin.com/sso',
127             );
128 0           my $uri = URI->new('https://sso.garmin.com/sso/embed');
129 0           $uri->query_form(%sso_embed_params);
130 0           my $response = $ua->get($uri);
131 0 0         croak "Can't retrieve /sso/embed: " . $response->status_line
132             unless $response->is_success;
133              
134 0           my %signin_params = (
135             id => 'gauth-widget',
136             embedWidget => 'true',
137             gauthHost => 'https://sso.garmin.com/sso/embed',
138             service => 'https://sso.garmin.com/sso/embed',
139             source => 'https://sso.garmin.com/sso/embed',
140             redirectAfterAccountLoginUrl => 'https://sso.garmin.com/sso/embed',
141             redirectAfterAccountCreationUrl => 'https://sso.garmin.com/sso/embed',
142             );
143 0           $uri = URI->new('https://sso.garmin.com/sso/signin');
144 0           $uri->query_form(%signin_params);
145 0           $response = $ua->get($uri);
146 0 0         croak "Can't retrieve /sso/signin: " . $response->status_line
147             unless $response->is_success;
148             # get the CSRF token from the response, it's a hidden form field
149 0           my $csrf_token;
150 0 0         if ($response->decoded_content =~ /name="_csrf"\s+value="(.+?)"/) {
151 0           $csrf_token = $1;
152             } else {
153 0           croak "couldn't find CSRF token";
154             }
155              
156             # submit login form with email and password
157             $response = $ua->post($uri, Referer => "$uri", Content => {
158             username => $self->{username},
159             password => $self->{password},
160 0           embed => 'true',
161             _csrf => $csrf_token,
162             });
163 0 0         croak "Can't submit login page: " . $response->status_line
164             unless $response->is_success;
165 0           my $title;
166 0 0         if ($response->decoded_content =~ m:(.+):) {
167 0           $title = $1;
168             } else {
169 0           croak "couldn't find in login response"; </td> </tr> <tr> <td class="h" > <a name="170">170</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="171">171</a> </td> <td class="c0" > <a href="#172"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#171-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="172">172</a> </td> <td class="c0" > <a href="#174"> 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="173">173</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="174">174</a> </td> <td class="c0" > <a href="#175"> 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="175">175</a> </td> <td class="c0" > <a href="#176"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#175-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="176">176</a> </td> <td class="c0" > <a href="#178"> 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="177">177</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="178">178</a> </td> <td class="c0" > <a href="#184"> 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="179">179</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="180">180</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="181">181</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="182">182</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="183">183</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="184">184</a> </td> <td class="c0" > <a href="#190"> 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="185">185</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="186">186</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="187">187</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="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">   </td> </tr> <tr> <td class="h" > <a name="190">190</a> </td> <td class="c0" > <a href="#193"> 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="191">191</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="192">192</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="193">193</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#193-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="#194"> 0 </a> </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 class="c0" > <a href="#195"> 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="195">195</a> </td> <td class="c0" > <a href="#197"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#195-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="196">196</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="197">197</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="#198"> 0 </a> </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 class="c0" > <a href="#199"> 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="199">199</a> </td> <td class="c0" > <a href="#200"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#199-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="200">200</a> </td> <td class="c0" > <a href="#203"> 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="201">201</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="202">202</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="203">203</a> </td> <td class="c0" > <a href="#204"> 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="204">204</a> </td> <td class="c0" > <a href="#206"> 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="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 class="c0" > <a href="#207"> 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="207">207</a> </td> <td class="c0" > <a href="#208"> 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="208">208</a> </td> <td class="c0" > <a href="#210"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#208-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="209">209</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="210">210</a> </td> <td class="c0" > <a href="#211"> 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="211">211</a> </td> <td class="c0" > <a href="#212"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#211-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="212">212</a> </td> <td class="c0" > <a href="#216"> 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="213">213</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="214">214</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="215">215</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="216">216</a> </td> <td class="c0" > <a href="#217"> 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="217">217</a> </td> <td class="c0" > <a href="#218"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $access_token = $response_data->{access_token}; </td> </tr> <tr> <td class="h" > <a name="218">218</a> </td> <td class="c0" > <a href="#226"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $ua->default_header('Authorization', 'Bearer ' . $access_token); </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">   </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"> #$uri = 'https://connectapi.garmin.com/activitylist-service/activities/search/activities?limit=20&start=0'; </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"> #$response = $ua->get($uri); </td> </tr> <tr> <td class="h" > <a name="222">222</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="223">223</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="224">224</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="225">225</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="226">226</a> </td> <td class="c0" > <a href="#227"> 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="227">227</a> </td> <td class="c0" > <a href="#230"> 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="228">228</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="229">229</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> # save access token </td> </tr> <tr> <td class="h" > <a name="230">230</a> </td> <td class="c0" > <a href="#231"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#230-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if (open my $cache_fh, '>', $cache_path) { </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"> chmod 0600, $cache_fh; </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"> print $cache_fh $access_token, "\n"; </td> </tr> <tr> <td class="h" > <a name="233">233</a> </td> <td class="c0" > <a href="#238"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> close $cache_fh; </td> </tr> <tr> <td class="h" > <a name="234">234</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="235">235</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="236">236</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="237">237</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> sub _api { </td> </tr> <tr> <td class="h" > <a name="238">238</a> </td> <td class="c0" > <a href="#239"> 0 </a> </td> <td >   </td> <td >   </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--subroutine.html#238-1"> 0 </a> </td> <td >   </td> <td >   </td> <td class="s"> my $self = shift; </td> </tr> <tr> <td class="h" > <a name="239">239</a> </td> <td class="c0" > <a href="#240"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my ($api, %opts) = @_; </td> </tr> <tr> <td class="h" > <a name="240">240</a> </td> <td class="c0" > <a href="#243"> 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="241">241</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="242">242</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="243">243</a> </td> <td class="c0" > <a href="#244"> 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="244">244</a> </td> <td class="c0" > <a href="#246"> 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="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 class="c0" > <a href="#247"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $url = URI->new($self->{searchurl}); </td> </tr> <tr> <td class="h" > <a name="247">247</a> </td> <td class="c0" > <a href="#248"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $url->path($api); </td> </tr> <tr> <td class="h" > <a name="248">248</a> </td> <td class="c0" > <a href="#250"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $url->query_form(%opts); </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">   </td> </tr> <tr> <td class="h" > <a name="250">250</a> </td> <td class="c0" > <a href="#256"> 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="251">251</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="252">252</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="253">253</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="254">254</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="255">255</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="256">256</a> </td> <td class="c0" > <a href="#257"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $request = HTTP::Request->new('GET', $url, $headers); </td> </tr> <tr> <td class="h" > <a name="257">257</a> </td> <td class="c0" > <a href="#258"> 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="258">258</a> </td> <td class="c0" > <a href="#261"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#258-1"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> croak "Can't make $api request: " . $response->status_line </td> </tr> <tr> <td class="h" > <a name="259">259</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="260">260</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="261">261</a> </td> <td class="c0" > <a href="#271"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> return $json->decode($response->content); </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"> } </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"> =head2 profile </td> </tr> <tr> <td class="h" > <a name="265">265</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="266">266</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Returns the user's Garmin Connect profile </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">   </td> </tr> <tr> <td class="h" > <a name="268">268</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="269">269</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="270">270</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> sub profile { </td> </tr> <tr> <td class="h" > <a name="271">271</a> </td> <td class="c0" > <a href="#272"> 0 </a> </td> <td >   </td> <td >   </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--subroutine.html#271-1"> 0 </a> </td> <td class="c3" > <a href="blib-lib-WebService-GarminConnect-pm--subroutine.html#271-1"> 1 </a> </td> <td >   </td> <td class="s"> my $self = shift; </td> </tr> <tr> <td class="h" > <a name="272">272</a> </td> <td class="c0" > <a href="#298"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> return $self->_api("/userprofile-service/socialProfile"); </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"> } </td> </tr> <tr> <td class="h" > <a name="274">274</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="275">275</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="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"> Returns a list of activities matching the requested criteria. If no criteria </td> </tr> <tr> <td class="h" > <a name="278">278</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="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"> =over </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"> =item limit </td> </tr> <tr> <td class="h" > <a name="283">283</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="284">284</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="285">285</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="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 >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item pagesize </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"> (Optional) The number of activities to return in each call to Garmin </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"> Connect. (One call to this subroutine may call Garmin Connect several </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"> times to retrieve all the requested activities.) Defaults to 50. </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">   </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"> =back </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">   </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"> =cut </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">   </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"> sub activities { </td> </tr> <tr> <td class="h" > <a name="298">298</a> </td> <td class="c0" > <a href="#299"> 0 </a> </td> <td >   </td> <td >   </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--subroutine.html#298-1"> 0 </a> </td> <td class="c3" > <a href="blib-lib-WebService-GarminConnect-pm--subroutine.html#298-1"> 1 </a> </td> <td >   </td> <td class="s"> my $self = shift; </td> </tr> <tr> <td class="h" > <a name="299">299</a> </td> <td class="c0" > <a href="#300"> 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="300">300</a> </td> <td class="c0" > <a href="#303"> 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="301">301</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="302">302</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="303">303</a> </td> <td class="c0" > <a href="#304"> 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="304">304</a> </td> <td class="c0" > <a href="#307"> 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="305">305</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="306">306</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="307">307</a> </td> <td class="c0" > <a href="#308"> 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="308">308</a> </td> <td class="c0" > <a href="#309"> 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="309">309</a> </td> <td class="c0" > <a href="#310"> 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="310">310</a> </td> <td class="c0" > <a href="#311"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#310-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="311">311</a> </td> <td class="c0" > <a href="#312"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#311-1"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--condition.html#311-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="312">312</a> </td> <td class="c0" > <a href="#317"> 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="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"> } </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"> # Special case when the limit is smaller than one page. </td> </tr> <tr> <td class="h" > <a name="317">317</a> </td> <td class="c0" > <a href="#318"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#317-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="318">318</a> </td> <td class="c0" > <a href="#319"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#318-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="319">319</a> </td> <td class="c0" > <a href="#323"> 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="320">320</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="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">   </td> </tr> <tr> <td class="h" > <a name="323">323</a> </td> <td class="c0" > <a href="#326"> 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="324">324</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="325">325</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="326">326</a> </td> <td class="c0" > <a href="#329"> 0 </a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $data = $self->_api("/activitylist-service/activities/search/activities", start => $start, limit => $pagesize); </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"> # Add this set of activities to the list. </td> </tr> <tr> <td class="h" > <a name="329">329</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="#330"> 0 </a> </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 class="c0" > <a href="#332"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#330-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="331">331</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="332">332</a> </td> <td class="c0" > <a href="#333"> 0 </a> </td> <td class="c0" > <a href="blib-lib-WebService-GarminConnect-pm--branch.html#332-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="333">333</a> </td> <td class="c0" > <a href="#335"> 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="334">334</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="335">335</a> </td> <td class="c0" > <a href="#336"> 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="336">336</a> </td> <td class="c0" > <a href="#339"> 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="337">337</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="338">338</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="339">339</a> </td> <td class="c0" > <a href="#344"> 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="340">340</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="341">341</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="342">342</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="343">343</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="344">344</a> </td> <td class="c0" > <a href="#346"> 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="345">345</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="346">346</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="#348"> 0 </a> </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">   </td> </tr> <tr> <td class="h" > <a name="348">348</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="349">349</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="350">350</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="351">351</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="352">352</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="353">353</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="354">354</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="355">355</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="356">356</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="357">357</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="358">358</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="359">359</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="360">360</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="361">361</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="362">362</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="363">363</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="364">364</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="365">365</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="366">366</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="367">367</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="368">368</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="369">369</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="370">370</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="371">371</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="372">372</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="373">373</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="374">374</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="375">375</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="376">376</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="377">377</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="378">378</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="379">379</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="380">380</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="381">381</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="382">382</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="383">383</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="384">384</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="385">385</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="386">386</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="387">387</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="388">388</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="389">389</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="390">390</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="391">391</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="392">392</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="393">393</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="394">394</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="395">395</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="396">396</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="397">397</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="398">398</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="399">399</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="400">400</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="401">401</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="402">402</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="403">403</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="404">404</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="405">405</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="406">406</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="407">407</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="408">408</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="409">409</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="410">410</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>