#!/usr/bin/perl -w # track -- by greenfly # This program will print out tracking information for # a UPS or Fedex tracking number passed as an argument # usage: track [--nag] [-rss] [...] # # --nag will cause track to loop every $retry seconds, emailing # $email_address when the output changes # # --rss will create output in RSS use LWP::UserAgent; use HTTP::Request::Common; use HTML::Parser; use XML::RSS; use Date::Manip qw(ParseDate UnixDate Date_Cmp); our $fedex_lang = "english"; our $fedex_ctry = "us"; our $ups_lang = "en_US"; our $email_address = 'user@host.com'; our $nag; our $rss; our $rowcount; our $colcount; our @table; our @text; our @numbers; my $output; my $output_last; my $retry = 300; # how many seconds before we retry in nag mode my $subject; foreach(@ARGV) { if($_ eq "--nag") { $nag = 1; } elsif($_ eq "--rss") { $rss = 1; } else{ tr/a-z/A-Z/; push @numbers, $_; } } if($nag) { $output_last = ""; $subject = "Package Update for "; $subject .= join(", ", @numbers); while(1) { $output = ""; foreach(@numbers) { $output .= tracker($_); } if($output ne $output_last) { if($rss) { create_rss($output); } else { if($email_address eq 'user@host.com') { die "change the email address from user\@host.com to a real address\n"; } else { system("echo -e \"$output\" | mail -s \"$subject\" $email_address"); } } } $output_last = $output; sleep $retry; } } else { if($rss) { foreach(@numbers) { $output .= tracker($_); } create_rss($output); } else { foreach(@numbers) { print tracker($_); } } } sub tracker { my $number = shift; my $output; $ups = $fedex = 0; $page = ""; $rowcount = -1; $colcount = 0; @table = (); @text = (); if($number =~ /^1Z[0-9A-Za-z]+$/) { $page = ups($number); $ups=1; } elsif($number =~ /^\d{12,}$/) { $page = fedex($number); $fedex=1; } elsif($number =~ /^\d{11}$/) { $page = purolator($number); $purolator=1; } # set up our html parser my $p = HTML::Parser->new(api_version => 3, start_h => [\&t_start_handler, "self,tagname,attr"], end_h => [\&t_end_handler, "self,tagname,attr"], report_tags => [qw(tr td th)], ); $p->parse($page || die) || die $!; # at this point the %table array of arrays should be populated #foreach $row (0 .. $#table) #{ # if($table[$row]){ foreach(@{$table[$row]}){print "$_\t";} print "\n"; } #} #die; # start outputting my $start = 0; foreach $row (0 .. $#table) { last if($start && $fedex && (! defined $table[$row])); next if($start && $ups && (! defined $table[$row])); last if($start && !$purolator && ($table[$row][0] =~ /Get Notified/)); next if($table[$row] && $fedex && $table[$row][0] =~ /gotrack/); next if($table[$row] && $fedex && $table[$row][0] =~ /function/); next if($table[$row] && $ups && $table[$row][0] =~ /function/); if($table[$row] && ($table[$row][0] =~ /^Scan|Location|Date\/Time/i)) { $start = 1; } if($start == 1) { #uncomment if you want to pretty up the output #if($table[$row][0] =~ /^\W+$/){print "\t";} foreach $col (@{ $table[$row] }) { if($purolator){ $col =~ s/var validate.*//; } if($ups){ $col =~ s/Tracking results.*//; } #uncomment if you want to pretty up the output # if($col eq "Time"){print "\t";} # if($col eq "Status"){print "\t\t";} # if($ups && $col eq "Location"){print "\t";} # if($fedex && $col eq "Location"){print "\t\t\t";} # print "$col\t"; $output .= "$col\t"; } # print "\n"; $output .= "\n"; } } # print "-" x 80; $output .= "-" x 80; # print "\n"; $output .= "\n"; return $output; } ############################################################ # subroutines start here ############################################################ sub t_start_handler { my($self, $tag, $attr) = @_; if($tag eq 'tr') { $rowcount++; $colcount = 0; } if($tag eq 'td' || $tag eq 'th') { $self->handler(text => \&hash_text, "dtext"); $colcount++; } } sub hash_text { my $text = shift; chomp $text; $text =~ s/\s{2,}//g; #print $text; if($text =~ /^$/){ return }; if($text =~ /^\s+$/){ return }; push(@{ $table[$rowcount] }, $text); } sub t_end_handler { my($self, $tag) = @_; $self->handler("text", undef); $self->handler("start", \&t_start_handler); $self->handler("end", undef); } sub fedex { my $tracking_number = shift; my $ua = new LWP::UserAgent; #my $req = new HTTP::Request GET => "http://www.fedex.com/Tracking?action=track&language=$fedex_lang&ascend_header=1&cntry_code=$fedex_ctry&initial=x&mps=y&tracknumbers=$tracking_number"; #my $req = new HTTP::Request GET => "http://fedex.com/Tracking?ascend_header=1&clienttype=dotcom&cntry_code=$fedex_ctry&language=$fedex_lang&tracknumbers=$tracking_number"; my $req = new HTTP::Request GET => "http://fedex.com/Tracking/Detail?ftc_start_url=&totalPieceNum=&backTo=&template_type=print&cntry_code=$fedex_ctry&language=$fedex_lang&trackNum=$tracking_number&pieceNum="; my $res = $ua->request($req); if($res->is_success) { return $res->content; } else { return 0; } } sub ups { my $tracking_number = shift; my %form; my $ups; # first we have to get the opening tracking page and pull some hidden # fields for the actual POST to get details my $ua = new LWP::UserAgent; my $res = $ua->request(GET "http://wwwapps.ups.com/WebTracking/processRequest?HTMLVersion=5.0&sort_by=status&tracknums_displayed=5&TypeOfInquiryNumber=T&loc=$ups_lang&InquiryNumber1=$tracking_number&InquiryNumber2=&InquiryNumber3=&InquiryNumber4=&InquiryNumber5=&AgreeToTermsAndConditions=yes&track.x=13&track.y=3&button_index=1"); if($res->is_success) { #$ups = $res->content; return $res->content; } else { return 0; } # grep through the web page we got and throw the hidden fields in to a hash # along with the submission fields that are POSTed while($ups =~ //g) { $form{$1} = $2; } $form{'tdts1.x'} = "26"; $form{'tdts1.y'} = "10"; # POST and get the actual UPS page $ua = new LWP::UserAgent; $ua->agent('Mozilla/5.0'); $res = $ua->request(POST 'http://wwwapps.ups.com/WebTracking/processRequest', \%form); if($res->is_success) { return $res->content; } else { return 0; } } sub purolator { my $tracking_number = shift; my $ua = new LWP::UserAgent; my $req = new HTTP::Request GET => "http://shipnow.purolator.com/shiponline/track/moreDetailsWeb.asp?pin=$tracking_number"; my $res = $ua->request($req); if($res->is_success) { return $res->content; } else { return 0; } } sub create_rss { my $output = shift; my @tracking = reverse @numbers; my $number; my $line; my $url; my $temp_date = &UnixDate("today", "%Y-%m-%dT%H:%M%z"); $temp_date =~ s/(.*)(\d\d)$/$1:$2/; my $rss = new XML::RSS (version => '1.0'); $rss->channel(title => "Package Tracking", link => "http://your_url_here", description => "Package Tracking using http://greenfly.org/track", dc => { language => "en-us", date => "$temp_date", lastBuildDate => "$temp_date", creator => "greenfly" }, ); foreach $line (split /-{80}\n/, $output) { $number = pop(@tracking); last unless($number); if($number =~ /^1Z[0-9A-Za-z]+$/) #UPS { $url = "http://wwwapps.ups.com/WebTracking/processRequest?HTMLVersion=5.0&sort_by=status&tracknums_displayed=5&TypeOfInquiryNumber=T&loc=$ups_lang&InquiryNumber1=$number&InquiryNumber2=&InquiryNumber3=&InquiryNumber4=&InquiryNumber5=&AgreeToTermsAndConditions=yes&track.x=13&track.y=3&button_index=1"; } elsif($number =~ /^\d{12,}$/) #FedEx { $url = "http://www.fedex.com/cgi-bin/tracking?action=track&language=$fedex_lang&ascend_header=1&cntry_code=$fedex_ctry&initial=x&mps=y&tracknumbers=$number"; } elsif($number =~ /^\d{11}$/) #Purolator { $url = "http://shipnow.purolator.com/shiponline/track/moreDetailsWeb.asp?pin=$number"; } # strip some bad chars $url =~ s/\&(?!amp;)/\&/g; $line =~ s/[\xa0]//mgs; @lines = split /\n/, $line; for($i = 1; $i <= $#lines; $i++) { $rss->add_item( title => "$number - $lines[$i]", link => "$url", description => "$lines[$i]" ); } } print $rss->as_string; }