Hack 86 Track Items in Your Watching List

figs/expert.giffigs/hack86.gif

Link an off-eBay auction tracker with eBay's Items I'm Watching list.

eBay provides the Items I'm Watching list (in My eBay Bidding/Watching) to help you keep track of auctions on which you haven't yet bid. A corresponding API call, GetWatchList, allows you to access the contents of that list.

But the Items I'm Watching list is rather limited and can be replaced with a custom tracking list, as described in [Hack #24]. Although the hack works, there are two simple ways to use the eBay API to make the script more robust and efficient:

  • Retrieve the title and end date with the GetItem API call instead of using the flakier method of extracting them from the auction page title.

  • Supplement the tracking list with any auctions in the Items I'm Watching list.

The following is a revised auction tracking script with both of these improvements.

This script requires all the Perl modules specified in [Hack #24], as well as Time::Local, by Tom Christiansen, Graham Barr, and Dave Rolsky (search.cpan.org/perldoc?Time::Local), used to convert dates retrieved from the API from GMT to local time.

#!/usr/bin/perl
require 'ebay.pl';
use Time::ParseDate;
use Time::Local;
use POSIX qw(strftime);
require("cgi-lib.pl");

&ReadParse;
$selfurl = "http://www.ebayhacks.com/exec/track.pl";
$localfile = "ebaylist.txt";
$timeoffset = 0;
@formatting=("color=#EE0000 STYLE=font-weight:bold",
                "color=#000000 STYLE=font-weight:bold", "color=#000000");
$i = 0;
$exists = 0;
$numlevels = 2;

# *** read stored list ***
open (INFILE,"$localdir/$localfile");
  while ( $line = <INFILE> ) {
    $line =~ s/\s+$//;
    $i++;
    ($enddate[$i],$priority[$i],$item[$i],$title[$i])=split(",", $line, 4);
    if (($item[$i] ne "") && ($item[$i] eq $in{'item'})) { $exists = $i; }
  }
close (INFILE);

# *** add latest auction if specified ***
if (($in{'auction'} =~ "ebay.com") && ($in{'item'} != "") && ($exists==0)) { 
my $rsp = call_api({ Verb => 'GetItem',     [1]
                DetailLevel => 0,
                         Id => $in{'item'}
  });

  if (! $rsp->{Errors}) {
    $i++;
    $item[$i] = $in{'item'};
    $title[$i] = $rsp->{Item}[0]{Title};     [2]
    $priority[$i] = 2;
    $enddate[$i] = timegm(localtime(parsedate($rsp->{Item}[0]{EndTime})));     [3]
  }
}
elsif (($in{'do'} eq "promote")) {
  $priority[$exists]--;
  if ($priority[$exists] < 0) { $priority[$exists] = 0; }
}
elsif (($in{'do'} eq "demote")) {
  $priority[$exists]++;
  if ($priority[$exists] > 2) { $priority[$exists] = 2; }  
}
elsif (($in{'do'} eq "delete")) {
  splice @enddate, $exists, 1;
  splice @priority, $exists, 1;
  splice @item, $exists, 1;
  splice @title, $exists, 1;
  $i--;
}

# *** scan watch list  ***
my $rsp = call_api({ Verb => 'GetWatchList',     [4]
              DetailLevel => 0,
                   UserId => $user_id,
                   SiteId => $site_id,
});
if (! $rsp->{Errors}) {
  $ebaytime = $rsp->{EBayTime};     [5]
  foreach (@{$rsp->{WatchList}{Items}{Item}}) {
    my %ii = %$_;
    ($id, $title, $timeleft) = @ii{qw/Id Title TimeLeft/};

    $seconds_left = $timeleft->{Days}*86400 +
                    $timeleft->{Hours}*3600 +
                    $timeleft->{Minutes}*60 +
                    $timeleft->{Seconds};
    $alreadythere = grep /$id/, @item;     [6]

    if ($alreadythere == 0) {
      $i++;
      $item[$i] = $id;
      $title[$i] = $title;
      $priority[$i] = 2;
      $enddate[$i] =     [7]
                 timegm(localtime(parsedate($ebaytime) + $seconds_left));
      $in{'do'} = "silentadd";
    }
  }
}

# *** update list ***
if (($in{'do'} ne "")) {
  open (OUTFILE,">$localdir/$localfile");
    for ($j = 1; $j <= $i; $j++) {
      print OUTFILE "$enddate[$j],$priority[$j],$item[$j],$title[$j]\n";
    }
  close (OUTFILE);

  if ($in{'do'} ne "silentadd") {
    print "Location: $selfurl\n\n";
    exit( 0);
  }
}

# *** sort list ***
@idx = sort criteria 0 .. $i;

# *** display list ***
print "Content-type: text/html\n\n";
print "<table border cellspacing=0 cellpadding=6>\n";

for ($j = 1; $j <= $i; $j++) {
  $formatteddate = strftime("%a, %b %d - %l:%M:%S %p",
                                         localtime($enddate[$idx[$j]]));
  $formattedtitle = "<a href=\"$itemurl$item[$idx[$j]]\" target=\"_blank\">
       <font $formatting[$priority[$idx[$j]]]>$title[$idx[$j]]</font></a>";

  if (strftime("%v", localtime($enddate[$idx[$j]])) eq
                                       strftime("%v", localtime(time))) {
    $formattedtitle = "<li>" . $formattedtitle;

  }
  if ($enddate[$idx[$j]] < time) {
    $formattedtitle = "<strike>" . $formattedtitle . "</strike>";

  }
  else {
    $timeleft = ($enddate[$idx[$j]] - time) / 60 + ($timeoffset * 60);
    if ($timeleft < 24 * 60) {
      $hoursleft = int($timeleft / 60);
      $minleft = int($timeleft - ($hoursleft * 60));
      if ($minleft < 10) { $minleft = "0" . $minleft; }
      $formattedtitle = $formattedtitle .
                      " <font size=-1>($hoursleft:$minleft left)</font>";

    }
  }

  print "<tr><td>$formattedtitle</td>";
  print "<td><font size=-1>$formatteddate</font></td>";
  print "<td><a href=\"$selfurl?item=$item[$idx[$j]]&do=promote\">+</a>";

  print " | <a href=\"$selfurl?item=$item[$idx[$j]]&do=demote\">-</a>";
  print " | <a href=\"$selfurl?item=$item[$idx[$j]]&do=delete\">x</a></td>";
  print "</tr>\n";
}

print "</table>\n";

sub criteria {
  # *** sorting criteria subroutine ***
  return ($priority[$a] <=> $priority[$b] or $enddate[$a] <=> $enddate[$b])
}

Although much of this script is documented in [Hack #24], a few minor changes have been made to accommodate the new portions that deal with the API. In other words, it's the same, but different.

First, when a new listing is added through the JavaScript link (from [Hack #24]), only the item number, $in{'item'}, is used. The GetItem API call (line [1]) uses the item number to retrieve the title [2] and end date [3]. Note that the end date has to be converted from GMT to local time, with the help of the timegm function in Time::Local. This process is much more robust and reliable (albeit ultimately a little slower) than the original method of parsing the page title.

Next, the GetWatchList API call retrieves the contents of your Items I'm Watching list [4], and checks to see if the item number is already in the list [6].

Unfortunately, the result set doesn't include EndTime, so unless we want to call GetItem a bunch of times, we need to be a little creative. All we have to do is retrieve eBay time from the EBayTime field (line [5]; note the capitalization) and add it to TimeLeft [7] for each auction. (It kind of feels like cheating, but it works.) Once again, this time needs to be converted from GMT to local time.

Every time you "watch" a new auction, just reload track.pl to import it into your list. Note that at the time of this writing there was no way to modify the watching list, so the script will simply retrieve the same list over and over. Presumably, you'll want to clear out the Items I'm Watching list (My eBay Bidding/Watching) as soon as they appear in your tracking list.

See [Hack #94] for details on setting up this script in your cgi-bin directory.