#!/usr/bin/perl -w use strict; use XML::Simple; use LWP::UserAgent; use HTML::Entities; use HTML::TableExtract; use HTML::Template; use DBI; use Net::FTP; # --- Config section --- # # !!! # You will need to fill out the DB and FTP info sections. # !!! # once Berkeley fixes their malformed XML, set this to 0 my $malformed_xml = 1; # Seti@home URL info my %seti = ( baseurl => 'http://setiathome.ssl.berkeley.edu/', teamlookup => 'fcgi-bin/fcgi?cmd=team_lookup_xml&name=', topteams => 'stats/team/team_type_0.html', ); # DB info my %db = ( type => 'mysql', name => 'seti', host => '', user => '', pass => '', ); # FTP info my %ftp = ( host => '', user => '', pass => '', dir => '', file => 'teams.html', ); # Number of teams to track my $num_teams = 40; # --- End of config section --- # my $dbh = DBI->connect("DBI:$db{type}:$db{name}:$db{host}", $db{user}, $db{pass}, { PrintError => 0}) || die $DBI::errstr; # To enable debugging, set an environment variable named debug to a true value # or add a command line parameter that has a true value my $debug = $ARGV[0] || $ENV{debug} || 0; print "Getting Top Teams...\n" if $debug; my @teams = GetTeams(); print "Parsing Teams...\n" if $debug; my ($static, $dynamic) = ParseTeams(@teams); $dbh->disconnect(); # Begin creation of HTML file that will be uploaded print "Creating HTML file...\n" if $debug; my $time = gmtime(); my (@stemp, @dtemp); for my $num (0..$#teams) { my $teamname = $$dynamic{$num}; my $bteam = $$static{$teamname}; my $team = $$dynamic{$teamname}; my %class = ( rank => '', members => '', results => '', ); if ($$team{rank} > $$bteam{rank}) { $class{rank} = 'red'; } elsif ($$team{rank} < $$bteam{rank}) { $class{rank} = 'green'; } if ($$team{members} > $$bteam{members}) { $class{members} = 'green'; } elsif ($$team{members} < $$bteam{members}) { $class{members} = 'red'; } if ($$team{results} > $$bteam{results}) { $class{results} = 'green'; } elsif ($$team{results} < $$bteam{results}) { $class{results} = 'red'; } push @dtemp, { rank => $$team{rank}, name => $teamname, members => $$team{members}, results => $$team{results}, c => $class{rank}, cm => $class{members}, cr => $class{results} }; $teamname = $$static{$num}; $bteam = $$static{$teamname}; push @stemp, { rank => $$bteam{rank}, name => $teamname, members => $$bteam{members}, results => $$bteam{results} }; } # open the html template for the Rank Standings Page my $top = HTML::Template->new(filename => 'top.tmpl'); # fill in the parameters $top->param(NUM_TEAMS => $num_teams); $top->param(TIME => $time); $top->param(STEMP => [@stemp]); $top->param(DTEMP => [@dtemp]); print "Writing the HTML file...\n" if $debug; # create the Top Teams Page open(HTML, '>', $ftp{file}); print HTML $top->output; close(HTML); print "Uploading the HTML file...\n" if $debug; UploadTeams($ftp{host}, $ftp{user}, $ftp{pass}, $ftp{dir}, $ftp{file}); exit; # Teams processing subroutines sub GetTeams { my $url = $seti{baseurl}.$seti{topteams}; print "Grabbing top teams: $url\n" if $debug; my $html = GetURL($url); my $te = new HTML::TableExtract( headers => [qw(Name Members Results Total Average)]); $te->parse($html); my @teams; my $lastteam = ''; my $rank = 0; foreach my $ts ($te->table_states) { foreach my $row ($ts->rows) { my ($team) = $$row[0] =~ /\d+\)\s+(.*)\s*$/; next if ($team eq $lastteam); $rank++; print "$rank $team\n" if ($debug && $rank <= $num_teams); push(@teams, $team) if ($rank <= $num_teams); $lastteam = $team; } } return @teams; } sub ParseTeams { my @teams = @_; # @static will hold the static data # @dynamic will hold the dynamic data # using arrays until sorting complete my (@static, @dynamic); for my $num (0..$#teams) { sleep(15); my $name = $teams[$num]; # team name my $url = $seti{baseurl}.$seti{teamlookup}.$name; print "Team URL: '$url'\n" if $debug; my $file = GetURL($url); $file = FixXML($file) if $malformed_xml; WriteXML("$name.xml", $file) if $debug; my ($bmembers, $bresults, $members, $results) = ParseTeamXML($file); push(@static, [$name, $bmembers, $bresults]); push(@dynamic, [$name, $members, $results]); } # sort on the results in @dynamic @dynamic = sort { $b->[2] <=> $a->[2] } @dynamic; # sorting complete, move the data into hashes for easier lookup my (%static, %dynamic); for my $num (0..$#teams) { my ($bname, $bmembers, $bresults) = @{$static[$num]}; my ($name, $members, $results) = @{$dynamic[$num]}; my $rank = $num + 1; $static{$bname} = {rank => $rank, members => $bmembers, results => $bresults}; $dynamic{$name} = {rank => $rank, members => $members, results => $results}; $static{$num} = $bname; $dynamic{$num} = $name; InsertTeamValues($dbh, "bteams", $rank, $bname, $bmembers, $bresults); InsertTeamValues($dbh, "teams", $rank, $name, $members, $results); } return (\%static, \%dynamic); } # LWP subroutines sub GetURL { my $url = shift; my $ua = new LWP::UserAgent( keep_alive => 1, timeout => 240, ); $ua->agent('KWSNStats/0.4'); my $file; my $pagelen; until ($file and $pagelen > 0) { sleep(15); print "Grabbing $url\n" if $debug; my $req = HTTP::Request->new(GET => $url); my $res = $ua->request($req); print $res->code, " ", $res->message,"\n" if $debug; if ($res->is_success) { $file = $res->content; $pagelen = length($file); } else { warn "Unable to get $url"; $pagelen = -1 } print "page length: $pagelen\n" if $debug; } return $file; } # XML subroutines sub FixXML { # fix Berkeley's malformed XML and remove some unnecessary info my $xml = shift; $xml =~ s!.*?!!sg; $xml =~ s!.*?!!sg; $xml =~ s!.*?!!sg; $xml =~ s!.*?!!sg; $xml =~ s!.*?!!sg; $xml =~ s!.*?!!sg; $xml =~ s/\n+/\n/g; $xml =~ s/[|]/ /g; # turn chr(127) and chr(27) into spaces my @xml = split('\n', $xml); $xml = ''; for my $line (@xml) { my ($name) = $line =~ m!(.*)!; if (defined $name) { print $name,$/ if $debug; encode_entities($name, '<&>³'); $line = "$name<\/name>"; } $xml .= $line."\n"; } $xml =~ s!³!3!g; return $xml; } sub ParseTeamXML { my $xml = shift; my $xs = new XML::Simple( keyattr => 'topmembers'); my $ref = $xs->XMLin($xml); my $bresults = $$ref{numresults}; my $bmembers = $$ref{nummembers}; my $results = 0; my @members = @{ $ref->{topmembers}{member} }; my $members = scalar @members; for my $member (@members) { $results += $$member{numresults}; } if ($debug) { print "Berkeley Members: $bmembers\n"; print "Berkeley Results: $bresults\n"; print "XML Members: $members\n"; print "XML Results: $results\n"; } return ($bmembers, $bresults, $members, $results); } sub WriteXML { my $file = shift; my $xml = shift; print "Writing XML out to $file.\n"; open(XML, '>', $file); print XML $xml; close(XML); } # DB subroutines sub InsertTeamValues { my ($dbh, $table, $rank, $team, $members, $results) = @_; my $sth = $dbh->prepare("INSERT INTO $table (rank, team, members, results, datetime) VALUES (?,?,?,?,NOW())") or die $dbh->errstr; $sth->execute($rank, $team, $members, $results) or die $sth->errstr; } # FTP subroutines sub UploadTeams { my ($host, $user, $pass, $dir, $file) = @_; my $ftp = Net::FTP->new($host, user => $user, pass => $pass, Debug => $debug); $ftp->login($user, $pass); $ftp->cwd($dir); $ftp->put($file); $ftp->quit; } #### Sir Muskrat's Top <TMPL_VAR NAME=NUM_TEAMS> Seti@home Teams

Sir Muskrat's Top Seti@home Teams

Last updated:

Static Top Teams
RankTeam NameMembersResults
Dynamic Top Teams
RankTeam NameMembersResults

Static is calculated by Berkeley. Dynamic is calculated by adding up the results for all members.
Red means the dynamic number is lower than the static while green indicates a higher number.