File Coverage

blib/lib/Geo/Caching.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


\s*\s*}gsi; }gsi;
line stmt bran cond sub pod time code
1             package Geo::Caching;
2              
3 1     1   22795 use strict;
  1         3  
  1         33  
4 1     1   4 use warnings;
  1         1  
  1         24  
5 1     1   1200 use WWW::Mechanize;
  1         315506  
  1         47  
6 1     1   530 use Geo::Cache;
  0            
  0            
7             use Geo::Gpx;
8             use XML::Simple;
9              
10             # Docs {{{
11              
12             =head1 NAME
13              
14             Geo::Caching - Object interface for querying Geocaching.com website
15              
16             =head1 SYNOPSIS
17              
18             use Geo::Caching;
19             my $gc = new Geo::Caching(
20             login => 'casey', # Your Geocaching username
21             password => 'mypass', # Your Geocaching password
22             max_results => 500, # Max number of caches to return
23             cache_days => 3, # Cache results for 3 days
24             cache_dir => '/tmp/geocache' #directory to cache into
25             );
26              
27             ### Get one Geo::Cache
28             my $cache = $gc->get('GCMMVH');
29              
30             ### Get Geo::Cache list that my user found
31             my @caches = $gc->query(
32             type => 'UL',
33             username => 'cpnkr,
34             );
35            
36              
37             #### List of valid query types
38             ####################################
39             # ZIP => By Postal Code
40             # WPT => By Coordinate
41             # UL => By Username (Found)
42             # U => By Username (Hidden)
43             # WN => By Waypoint Name
44             ####################################
45             ####
46              
47             =head1 DESCRIPTION
48              
49             Provide an object interface to query Geocaching.com
50              
51             =head1 AUTHOR
52              
53             Casey Lee
54             cplee@cplee.org
55              
56             =head1 COPYRIGHT
57              
58             This program is free software; you can redistribute
59             it and/or modify it under the same terms as Perl itself.
60              
61             The full text of the license can be found in the
62             LICENSE file included with this module.
63              
64             =cut
65              
66             # }}}
67              
68             use vars qw($VERSION $AUTOLOAD);
69             $VERSION = '0.11';
70              
71             # sub new {{{
72              
73             sub new {
74             my $class = shift;
75             my $params = { @_ };
76             my $self = {};
77             my %config = ( login => ($params->{login} || ''),
78             password => ($params->{password} || ''),
79             max_results => ($params->{max_results} || 500),
80             sleep => ($params->{sleep} || 1),
81             cache_days => ($params->{cache_days} || 1),
82             cache_dir => ($params->{cache_dir} || '/tmp/geocache'),
83             );
84            
85             $self = bless( \%config, ref($class) || $class );
86              
87             return ($self);
88             } # }}}
89              
90             # AUTOLOADER {{{
91              
92             sub AUTOLOAD {
93             my $self = shift;
94             my $val = shift;
95             my ( $method );
96             ( $method = $AUTOLOAD ) =~ s/.*:://;
97              
98             if (defined $val) {
99             $self->{$method} = $val;
100             } else {
101             # Use the existing value
102             }
103              
104             return $self->{$method};
105             } # }}}
106              
107              
108             sub get {
109             my $self = shift;
110             my $wpt = shift;
111              
112             ##########################
113             my $login_url = 'http://www.geocaching.com/login/default.aspx';
114             my $details_url = 'http://www.geocaching.com/seek/cache_details.aspx';
115             ##########################
116              
117             my $login = $self->{login};
118             my $password = $self->{password};
119              
120             my $mech = new WWW::Mechanize(cookie_jar => {});
121              
122             # login to geocaching.com
123             $mech->get($login_url);
124             $mech->field('myUsername', $login);
125             $mech->field('myPassword', $password);
126             $mech->click_button(value => 'Login');
127              
128             # get the user's caches
129             $mech->get("$details_url?WP=$wpt");
130             my $res = $mech->click_button(name => 'btnGPXDL');
131             my @caches = $self->parse_gpx(xml => $res->content());
132              
133             return $caches[0];
134             }
135              
136             sub query {
137             my $self = shift;
138             my $args = {@_};
139              
140             ##########################
141             my $login_url = 'http://www.geocaching.com/login/default.aspx';
142             my $nearest_url = 'http://www.geocaching.com/seek/nearest.aspx';
143             ##########################
144              
145             my $login = ($args->{login} || $self->{login});
146             my $password = ($args->{password} || $self->{password});
147             my $sleep = ($args->{sleep} || $self->{sleep});
148             my $max = ($args->{max_results} || $self->{max_results} || 500);
149             my $type = $args->{type};
150             my $cache_list = ($args->{cache_list} || []);
151             my $cache_dir = ($args->{cache_dir} || $self->{cache_dir});
152             my $cache_days = ($args->{cache_days} || $self->{cache_days});
153             my $no_cache = $args->{no_cache};
154              
155             my $query;
156              
157             `mkdir -p $cache_dir`;
158              
159             ####################################
160             # ZIP => By Postal Code
161             # WPT => By Coordinate
162             # SC => By State/Country
163             # KW => By Keyword
164             # UL => By Username (Found)
165             # U => By Username (Hidden)
166             # WN => By Waypoint Name
167             ####################################
168              
169             if($type eq 'ZIP') {
170             my $zip = $args->{zipcode};
171             if($zip =~ /^\d{5}$/) {
172             $query = "ZIP=$zip";
173             }
174             } elsif ($type eq 'WPT') {
175             my $lat = $args->{lat};
176             my $lon = $args->{lon};
177              
178             if($lat =~ /^[-\d\.]+$/ &&
179             $lon =~ /^[-\d\.]+$/) {
180             $query = "LAT=$lat&LON=$lon";
181             }
182             } elsif ($type eq 'SC') {
183             } elsif ($type eq 'KW') {
184             } elsif ($type eq 'UL') {
185             my $user = $args->{username} || $self->{login};
186             $query = "UL=$user";
187             } elsif ($type eq 'U') {
188             my $user = $args->{username} || $self->{login};
189             $query = "U=$user";
190             } elsif ($type eq 'WN') {
191             my $wpt = $args->{waypoint};
192             if($wpt =~ /^GC(\w+)$/) {
193             $query = "WN=$wpt";
194             }
195             } else {
196             warn "Unsupported type: $type\n";
197             }
198              
199              
200             unless($query) {
201             warn "Error...bailing out";
202             return;
203             }
204              
205             ### caching
206             my $t_file = $query;
207             $t_file =~ s/[\.\/]//g;
208             my $t_path = "$cache_dir/$t_file";
209              
210              
211             ### Use the cache
212             if(!$no_cache
213             && (-e $t_path)
214             && (-M $t_path < $cache_days))
215             {
216             my $content;
217             open (F, $t_path);
218             while() {$content .= $_};
219             close(F);
220             $self->parse_gpx(xml => $content,
221             cache_list => $cache_list,
222             );
223             return @$cache_list;
224             }
225              
226              
227             if($query =~ /^WN/) {
228             push @$cache_list, $self->get($args->{waypoint});
229             } else {
230             my $mech = new WWW::Mechanize(cookie_jar => {});
231             my $cache_attribs = {};
232              
233             # login to geocaching.com
234             $mech->get($login_url);
235             $mech->field('myUsername', $login);
236             $mech->field('myPassword', $password);
237             $mech->click_button(value => 'Login');
238              
239             # get the user's caches
240             $mech->get("$nearest_url?$query");
241              
242             my $page = 1;
243             while((scalar @$cache_list) < $max) {
244              
245             ## Get some info about each cache
246             my $c = $mech->content;
247             $c =~ m{(.*?)
}is;
248             my $t = $1;
249             my @rows = $t =~ m{\s*\s*(.*?)
250             shift @rows;
251             foreach my $r (@rows) {
252             my @cells = $r =~ m{(.*?)
253             my $attribs = {};
254             my $name = '';
255              
256             ## force init of cells
257             for(my $ci=0; $ci<8; $ci++) {
258             $cells[$ci] ||= "";
259             }
260            
261             ## Get the cache name
262             if($cells[5] =~ /\((GC.+)\)/) {
263             $name = $1;
264             }
265              
266             ## Get the cache type
267             if( $cells[2] =~ /
268             $attribs->{type} = $1;
269             }
270              
271             ## Get the difficulty/terrain/size
272             if($cells[3] =~ /\(([\d\.]+)\/([\d\.]+)\).*title="Size: (.*)"/) {
273             $attribs->{difficulty} = $1;
274             $attribs->{terrain} = $2;
275             $attribs->{size} = $3;
276             }
277              
278             ## Get the dates
279             $attribs->{hidden_date} = $cells[4];
280            
281             if(my @fdates = $cells[6] =~ m{(\d{2} \w{3} \d{2})}gs) {
282             $attribs->{last_found_date} = $fdates[0];
283             $attribs->{user_found_date} = $fdates[1];
284             }
285              
286             # Get and chek the box, if it exists
287             if($cells[7] =~ //i) {
288             # warn "$name -> $1: ".join(',',%$attribs)."\n";
289             $mech->tick('CID',$1);
290              
291             ## add the attribs to a hash keyed by GCNAME
292             $cache_attribs->{$name} = $attribs;
293             }
294             else
295             {
296             }
297             }
298             if(0) {
299             my @images = $mech->find_all_images(
300             url_regex => qr/\/images\/WptTypes\/\d/);
301             my @sym;
302             foreach my $i (@images) {
303             my $a = $i->alt();
304             push @sym, $a;
305             }
306              
307             my $form = $mech->form_number(1);
308             my @cids = $form->find_input('CID','checkbox');
309             foreach my $cid (@cids) {
310             $cid->check();
311             }
312             }
313             my $res = $mech->click_button(value => 'Download Waypoints');
314             $self->parse_loc(xml => $res->content(),
315             cache_attribs => $cache_attribs,
316             cache_list => $cache_list);
317              
318             $mech->back();
319              
320              
321             my $next_link = $mech->find_link( text_regex => qr/Next/i );
322             if($next_link) {
323             my $url = $next_link->url();
324             if($url =~ /javascript:__doPostBack\('(.+)\$(.+)','(.*)'\)/) {
325             my $target = "$1:$2";
326             my $argument = $3;
327              
328             $mech->field('__EVENTTARGET',$target);
329             $mech->field('__EVENTARGUMENT',$argument);
330             $mech->submit();
331            
332             sleep $sleep if $sleep; # be nice to geocaching.com :)
333             }
334             } else {
335             last;
336             }
337             }
338             }
339              
340             if(open(F,">$t_path"))
341             {
342             my $gpx = new Geo::Gpx(@$cache_list);
343             print F $gpx->xml();
344             close(F);
345             }
346              
347              
348             return @$cache_list;
349             }
350              
351              
352              
353              
354             sub parse_gpx {
355             my $self = shift;
356             my $args = {@_};
357             my $xml = $args->{xml};
358             my $caches = $args->{cache_list} || [];
359             my $xs = new XML::Simple();
360             my $ref = $xs->XMLin($xml);
361             if(ref $ref->{wpt} eq 'ARRAY') {
362             foreach my $w (@{ $ref->{wpt} }) {
363             my $gc = new Geo::Cache(%$w);
364             push @$caches, $gc;
365             }
366             } elsif(ref $ref->{wpt} eq 'HASH') {
367             if(exists $ref->{wpt}->{name}) {
368             my $gc = new Geo::Cache(%{$ref->{wpt}});
369             push @$caches, $gc;
370             } else {
371             foreach my $k (keys %{ $ref->{wpt} }) {
372             my $w = $ref->{wpt}->{$k};
373             $w->{name} = $k;
374             my $gc = new Geo::Cache(%$w);
375             push @$caches, $gc;
376             }
377             }
378             }
379              
380             return @$caches;
381             }
382              
383             sub parse_loc {
384             my $self = shift;
385             my $args = {@_};
386             my $xml = $args->{xml};
387             my $caches = $args->{cache_list} || [];
388             my $xs = new XML::Simple();
389             my $ref = $xs->XMLin($xml);
390              
391             my $cache_attribs = $args->{cache_attribs} || {};
392              
393             if(ref $ref->{waypoint} eq 'ARRAY') {
394             foreach my $w (@{ $ref->{waypoint} }) {
395             my $attribs = $cache_attribs->{$w->{name}->{id}};
396             #warn $w->{name}->{id}."-->".join(",",%$attribs)."\n";
397             my $desc = $w->{name}->{content}." (".$attribs->{difficulty}."/".$attribs->{terrain}.")";
398             my $gc = new Geo::Cache(
399             lat => $w->{coord}->{lat},
400             lon => $w->{coord}->{lon},
401             name => $w->{name}->{id},
402             desc => $desc,
403             time => 0,
404             sym => 'Geocache',
405             type => ($w->{type}."|".($attribs->{type}||'Traditional Cache')),
406             url => $w->{link}->{content}, );
407             push @$caches, $gc;
408             }
409             } else {
410             my $w = $ref->{waypoint};
411             my $attribs = $cache_attribs->{$w->{name}->{id}};
412             my $desc = $w->{name}->{content}." (".$attribs->{difficulty}."/".$attribs->{terrain}.")";
413             my $gc = new Geo::Cache(
414             lat => $w->{coord}->{lat},
415             lon => $w->{coord}->{lon},
416             name => $w->{name}->{id},
417             desc => $desc,
418             time => 0,
419             sym => 'Geocache',
420             type => ($w->{type}."|".($attribs->{type}||'Traditional Cache')),
421             url => $w->{link}->{content}, );
422             push @$caches, $gc;
423             }
424              
425             return @$caches;
426             }
427              
428             1;
429              
430