#!/usr/bin/perl -w # # NextTram - Fetches information from Yarra Trams' TramTracker service # # Copyright (C) 2008 Paul Dwerryhouse # # Released under GPL v2. $PACKAGE="NextTram"; $VERSION="0.1"; use HTML::TableExtract; use Unicode::MapUTF8 qw(from_utf8); use LWP::UserAgent; use Date::Parse; if ($#ARGV < 0) { my $mesg = "Usage: $0 [ ...]\n\n"; $mesg .= "Find your stop number here:\n"; $mesg .= "\thttp://www.yarratrams.com.au/desktopdefault.aspx/tabid-236\n"; $mesg .= "\nExample: $0 1419\n"; die "$mesg"; } my $ua = LWP::UserAgent->new; $ua->agent("$PACKAGE/$VERSION "); my @trams; while (my $stop = shift) { my $html = get_times($ua,$stop); my $tram = parse_html($html); push @trams, @$tram; } @trams = sort { $a->{tram_time} <=> $b->{tram_time} } @trams; foreach my $tram (@trams) { print_tram($tram); } # This is needed because whoever made TramTracker doesn't understand the # concept of separating your data from the formatting. sub demoronise { my ($str) = @_; $str = "" if !defined($str); $str =~ s/[\n\r]//g; $str =~ s/^ +//; $str =~ s/ +$//; return $str; } sub get_times { my ($ua,$stop) = @_; my $req = HTTP::Request->new(POST => "http://www.yarratrams.com.au/ttweb/default.aspx"); $req->content_type('application/x-www-form-urlencoded'); $req->content("tkScriptManager=upnMain|btnPrediction&__EVENTTARGET=&__EVENTARGUMENT=&__LASTFOCUS=&__VIEWSTATE=&ddlRouteNo=Any&txtTrackerID=$stop&btnPrediction="); my $res = $ua->request($req); if (!$res->is_success) { die "Can't connect: $!"; } # The service returns utf8 text and HTML::TableExtract doesn't like that my $html = from_utf8({ -string => ($res->content), -charset => "ISO-8859-1"}); return $html; } # Demunge the time field, which can be in one of at least three formats; # either 'Now', 'X min(s)' or a date. Ignore the '!' symbol which implies # that the listed tram hasn't actually left the terminus, so TramTracker # doesn't know when it will arrive and is instead reading from the listed # timetables. sub parse_time { my ($time) = @_; if ($time =~ /(\d\d)-(\w\w\w)-(\d\d) (\d\d):(\d\d) (\w\w)/) { $time =~ s/ !//; $time = int ((str2time($time) - time) / 60); } elsif ($time eq 'Now') { $time = 0; } else { $time =~ s/ mins?.*$//; } return $time; } sub parse_html { my ($html) = @_; # We can't treat it as XML, because although Yarra Trams claim to be # using XHTML, it is riddled with errors and makes XML::Simple bork. my @trams; my $te = HTML::TableExtract->new( headers => [qw(Route Destination Arrival)] ); $te->parse($html); foreach my $ts ($te->tables) { foreach my $row ($ts->rows) { # Yes, this is dodgy. The table is full of rubbish for # display purposes, so right now I'm going to assume # that any row with three elements in it is data that # we can actually use. if (defined($row->[2])) { my $tram; $tram->{tram_route} = demoronise($row->[0]); $tram->{tram_dest} = demoronise($row->[1]); $tram->{tram_time} = parse_time( demoronise($row->[2])); push @trams, $tram; } } } return \@trams; } sub print_tram { my ($tram) = @_; print $tram->{tram_route} . ":" . $tram->{tram_dest} . ":" . $tram->{tram_time} . "\n"; }