#!/usr/bin/perl -w

# track -- by greenfly <greenfly@greenfly.org>
# This program will print out tracking information for 
# a UPS or Fedex tracking number passed as an argument
# usage: track [--nag] [-rss] <tracking number> [<tracking number>...]
# 
# --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 =~ /<INPUT TYPE="HIDDEN" NAME="(.*?)" VALUE="(.*?)">/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;)/\&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;
}

