aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterWebInterface/Handler/ServInfo.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/MasterWebInterface/Handler/ServInfo.pm')
-rwxr-xr-xlib/MasterWebInterface/Handler/ServInfo.pm550
1 files changed, 550 insertions, 0 deletions
diff --git a/lib/MasterWebInterface/Handler/ServInfo.pm b/lib/MasterWebInterface/Handler/ServInfo.pm
new file mode 100755
index 0000000..be30fd0
--- /dev/null
+++ b/lib/MasterWebInterface/Handler/ServInfo.pm
@@ -0,0 +1,550 @@
+package MasterWebInterface::Handler::ServInfo;
+use strict;
+use warnings;
+use utf8;
+use TUWF ':html';
+use POSIX 'strftime';
+use Exporter 'import';
+
+TUWF::register(
+ qr{(.[\w]{1,20})/([\:\.\w]{9,35})} => \&show_server,
+);
+
+################################################################################
+# Display server information
+# Verify if game and server (ip:hostport) exist. Display as many available
+# values as possible.
+# Display error pages if not found or incorrect.
+################################################################################
+sub show_server
+{
+ my ($self, $gamename, $s_addr) = @_;
+
+ # parse from ipv4/6 and soft sanity check
+ my ($ip, $port) = $self->from_addr_str($s_addr);
+
+ # select server from database
+ my $info = $self->dbGetServerInfo(
+ ip => $ip,
+ hostport => $port,
+ limit => 1,
+ )->[0];
+
+ # either redirect or show error when no info was found
+ if (!defined $info)
+ {
+ # try if query port was provided instead
+ my $attempt = $self->dbGetServerInfo(
+ ip => $ip,
+ port => $port,
+ limit => 1
+ )->[0];
+
+ # if it exists now, automatically redirect to this page (don't show info here)
+ if (defined $attempt && defined $attempt->{gamename} && defined $attempt->{hostport} )
+ {
+ $self->resRedirect("/$attempt->{gamename}/$ip:$attempt->{hostport}");
+ return;
+ }
+
+ # otherwise not found in database, soft error page (no 404 status)
+ $self->htmlHeader(title => 'Server not found');
+ $self->htmlSearchBox(title => "Servers", action => "/s", sel => 's', fq => '');
+
+ div class => "mainbox warning";
+ div class => "header";
+ h1 'Server not found';
+ p "The requested information is not in our database.";
+ end;
+
+ div class => "description";
+ p;
+ txt 'It seems the server you were looking for does not exist in our database,';
+ br;
+ txt 'perhaps our search function may yield results?';
+ end;
+
+ p;
+ txt "You tried to access ";
+ span class => "hilit", $self->to_ipv4_str($s_addr) // "[no ip]";
+ txt " in ";
+ span class => "hilit", $gamename;
+ txt ".";
+ end;
+ end;
+ end;
+ $self->htmlFooter;
+
+ return;
+ }
+
+
+ #
+ # info exists. sanity checks
+ $gamename = $info->{gamename} // $gamename;
+ my $gamedescription = $self->dbGetGameDesc($info->{gamename}) // $info->{gamename};
+
+ #
+ # generate info page
+ $self->htmlHeader(title => $info->{hostname} // "Server");
+ $self->htmlSearchBox(
+ title => "$gamedescription Servers",
+ action => "/s/$gamename",
+ sel => 's',
+ fq => ''
+ );
+
+ # serverinfo box
+ div class => "mainbox detail";
+
+ div class => "header";
+ h1 title => $info->{hostname} // "[unnamed $gamename server]",
+ $info->{hostname} // "[unnamed $gamename server]";
+ end;
+
+ #
+ # Map thumbnail and bot info
+ #
+ div class => "container";
+
+ # find the correct thumbnail, otherwise game default, otherwise 333 default
+ div class => "thumbnail";
+ my $mapfig = "/map/default/333networks.jpg";
+ my $mapfile = lc ($info->{mapname} // "");
+
+ # if map figure exists, use it
+ if (-e "$self->{root}/s/map/$gamename/$mapfile.jpg")
+ {
+ # map image
+ $mapfig = "/map/$gamename/$mapfile.jpg";
+ }
+ # if not, game default image
+ elsif (-e "$self->{root}/s/map/default/$gamename.jpg")
+ {
+ # game image
+ $mapfig = "/map/default/$gamename.jpg";
+ }
+ # otherwise 333networks default
+ else
+ {
+ # 333networks default
+ $mapfig = "/map/default/333networks.jpg";
+ }
+
+ # map title/name (not lowercase)
+ my $mapname = $info->{mapname} // $info->{maptitle} // "Untitled";
+ my $maptitle = ( $info->{maptitle} && lc $info->{maptitle} ne "untitled" )
+ ? $info->{maptitle}
+ : $mapname;
+
+ img src => $mapfig,
+ alt => $mapfig,
+ title => $mapname;
+ span $maptitle;
+ end;
+
+ table class => "mapinfo";
+ # numplayer field
+ Tr;
+ td class => "wc1", "Players:";
+ td;
+ txt $info->{numplayers} // 0;
+ txt "/";
+ txt $info->{maxplayers} // 0;
+ end;
+ end;
+
+
+ Tr;
+ td "Bots:";
+ td;
+ if ($info->{botskill} or $info->{minplayers})
+ {
+ txt $info->{minplayers} // 0;
+ txt " ";
+ txt $info->{botskill} // "";
+ txt " bot";
+ txt ($info->{minplayers} && $info->{minplayers} == 1 ? "" : "s");
+ }
+ else
+ {
+ txt "No";
+ }
+ end;
+ end;
+ end; #table
+ end; # container
+
+ #
+ # specific server entry information
+ table class => "serverinfo";
+ Tr;
+ th class => "wc1", title => "Server ID: " . ($info->{id} // "-1"), "Server Info";
+ th "";
+ end;
+
+ # server address
+ Tr;
+ td "Address:";
+ td title => $info->{queryport} // 0;
+ txt $self->to_ipv4_str($info->{ip}) // "0.0.0.0";
+ txt ":";
+ txt $info->{hostport} // 0;
+ end;
+ end;
+
+ # contact
+ if ($info->{adminname})
+ {
+ Tr;
+ td "Admin:";
+ td $info->{adminname};
+ end;
+ }
+
+ # always display contact
+ Tr;
+ td class => "wc1", "Contact:";
+ td;
+ if ($info->{adminemail})
+ {
+ txt $info->{adminemail}
+ }
+ else
+ {
+ i;
+ txt "This server has no contact information listed ";
+ a href => "https://ut99.org/viewtopic.php?f=33&t=6660", "[?]";
+ end;
+ }
+ end;
+ end;
+
+ # location data
+ Tr;
+ td class => "wc1", "Location:";
+
+ my ($flag, $country) = $self->countryflag($info->{country} // "");
+ td;
+ img class => "flag", src => "/flag/$flag.svg";
+ txt " ". $country;
+ end;
+ end;
+
+ # added / updated
+ Tr;
+ {
+ td "Added:";
+ my @t = gmtime( time - ( $info->{dt_added} // 0 ) );
+ my $sig = 0;
+ my $diff = "";
+ if ($t[5]-70)
+ {
+ $diff .= $t[5]-70
+ . " year"
+ . ( ($t[5]-70==1) ? "" : "s" );
+ $sig++;
+ }
+ if ($t[7])
+ {
+ $diff .= ($sig?", ":"")
+ . $t[7]
+ . " day"
+ . ( ($t[7]==1) ? "" : "s")
+ }
+
+ if ($diff eq "")
+ {
+ $diff = "Less than one day";
+ }
+ td $diff . " ago (" . (strftime "%e %b %Y", gmtime ($info->{dt_added} // 0) ) .")";
+ }
+ end;
+
+ Tr;
+ {
+ td "Last seen:";
+ td;
+ my @t = gmtime( time - ( $info->{dt_updated} // 0 ) );
+ if ($t[5]-70 // $t[7])
+ {
+ # more than 1 day? show date
+ span class => "r", (strftime "%e %b %Y", gmtime ($info->{dt_updated} // 0) );
+ }
+ else
+ {
+ # less than 1 day? show "time ago"
+ my $diff = "";
+ $diff .= ($t[2] ? $t[2]." hour" . ( $t[2]>1 ? "s, " : ", ") : "");
+ $diff .= ($t[1] ? $t[1]." minute". ( $t[1]>1 ? "s, " : ", ") : "");
+ $diff .= ($t[0] ? $t[0]." second". ( $t[0]>1 ? "s" : " " ) : "0 seconds");
+ $diff .= " ago";
+ span $diff;
+ }
+ end;
+ }
+ end;
+
+
+ # TODO: move flags to header
+ Tr;
+ td "Flags: ";
+ td;
+ # uplink/sync
+ span title => "direct uplink or manually added?",
+ ($info->{f_direct} ? "💻 uplink" : "🤚 manual");
+
+ txt ", ";
+
+ span title => "authenticated game server?",
+ ($info->{f_auth} ? "✅ authed" : "❌ insecure");
+ txt ", ";
+
+ span title => "server blacklisted for violating 333networks policy?",
+ ($info->{f_blacklist} ? "blacklisted" : "compliant");
+
+ txt ", ";
+ span title => "does the server require a password to join?",
+ ($info->{passworded} ?
+ ($info->{passworded} =~ /(true|1)/i ? "🔒 password" : "🔓 open")
+ : "🔓 open");
+ end;
+ end;
+ end; # table serverinfo
+
+ #
+ # Specific game and version information
+ table class => "gameinfo";
+ Tr;
+ th class => "wc1", "Game Info";
+ th "";
+ end;
+
+ Tr;
+ td "Game:";
+ td;
+ a href => "/s/$gamename", $gamedescription;
+ end;
+ end;
+ if ($info->{gametype})
+ {
+ Tr;
+ td "Type:";
+ td $info->{gametype};
+ end;
+ }
+ if ($info->{gamestyle})
+ {
+ Tr;
+ td "Style:";
+ td $info->{gamestyle};
+ end;
+ }
+ if ($info->{gamever})
+ {
+ Tr;
+ td "Version:";
+ td $info->{gamever};
+ end;
+ }
+ end; #gameinfo
+
+ #
+ # Mutator list
+ table class => "mutators";
+ Tr;
+ th "Mutators";
+ end;
+ Tr;
+ td;
+ if (defined $info->{mutators} && $info->{mutators} ne "None")
+ {
+ txt $info->{mutators};
+ }
+ else
+ {
+ i "This server does not have any mutators listed.";
+ }
+ end;
+ end;
+ end; #mutators
+
+ #
+ # Player info
+ table class => "players";
+ my $player = $self->dbGetPlayerInfoList(sid => $info->{id});
+ my %team = (0 => "#e66",
+ 1 => "#66e",
+ 2 => "#6e6",
+ 3 => "#ee6",
+ 4 => "#fe6",
+ 255 => "#aaa");
+
+ # iterate players and colors
+ Tr;
+ th class => "wc1", 'Player Info';
+ th class => "frags", 'Frags';
+ th class => "mesh", 'Mesh';
+ th class => "skin", 'Skin';
+ th class => "ping", 'Ping';
+ end;
+
+ for (my $i = 0; defined $player->[$i]->{name}; $i++)
+ {
+ # determine teamcolor
+ my $teamcolor = ( defined $player->[$i]->{team} &&
+ $player->[$i]->{team} =~ m/^([0-4]|255)$/i)
+ ? $team{$player->[$i]->{team}}
+ : "#aaa";
+
+ Tr $i % 2 ? (class => 'odd') : (), style => 'color:'.$teamcolor;
+ td class => "wc1", title => $player->[$i]->{team} // "None";
+ txt $player->[$i]->{name} // "[no name]";
+ if ($player->[$i]->{ngsecret} && $player->[$i]->{ngsecret} =~ m/^bot$/i)
+ {
+ txt " (bot)";
+ }
+ end;
+ td class => "frags", $player->[$i]->{frags} // 0;
+ td class => "mesh", $player->[$i]->{mesh} // "";
+ td class => "skin", $player->[$i]->{skin} // "";
+ td class => "ping", $player->[$i]->{ping} // 0;
+ end;
+ }
+ if ( ! defined $player->[0]->{name})
+ {
+ Tr;
+ td colspan => 5;
+ i "There is no player information available.";
+ end;
+ end;
+ }
+ end; # playerinfo
+
+ #
+ # Team info
+ table class => "teaminfo";
+ Tr;
+ th class => "wc1", "Team Info";
+ th "";
+ end;
+ Tr;
+ td "Balance Teams:";
+ td ( (defined $info->{balanceteams} &&
+ $info->{balanceteams} =~ m/true/i ) ? "Yes" : "No");
+ end;
+ Tr;
+ td "Players Balance Teams:";
+ td ( defined $info->{playersbalanceteams} &&
+ $info->{playersbalanceteams} ? "Yes" : "No");
+ end;
+ Tr;
+ td "Friendly Fire:";
+ td ($info->{friendlyfire} // "0%");
+ end;
+ Tr;
+ td "Max Teams:";
+ td ($info->{maxteams} // 1);
+ end;
+ end;
+
+ #
+ # Game Limits
+ table class => "limits";
+ Tr;
+ th class => "wc1", "Limits";
+ th "";
+ end;
+ Tr;
+ td "Time Limit:";
+ td (($info->{timelimit} // 0). " min");
+ end;
+ Tr;
+ td "Score Limit:";
+ td ($info->{goalteamscore} // 0);
+ end;
+ Tr;
+ td "Frag Limit:";
+ td ($info->{fraglimit} // 0);
+ end;
+ end;
+
+ #
+ # Share options (copy fields)
+ my $url = $self->{site_url}
+ . "/"
+ . $gamename
+ . "/"
+ . ( $self->to_ipv4_str($info->{ip}) // "0.0.0.0" )
+ . ":"
+ . ($info->{hostport} // 0);
+
+ table class => "shareopts";
+ Tr;
+ th class => "wc1", "Share";
+ th "";
+ end;
+ Tr;
+ td class => "tc1", "Link";
+ td class => "tc2";
+ input type => 'text',
+ class => 'text',
+ name => 'url',
+ value => $url;
+ end;
+ end;
+ Tr;
+ td class => "tc1";
+ a href => "/json",
+ title => "The url to access this server over the 333networks Json API",
+ "Json API";
+ end;
+ td class => "tc2";
+ input type => 'text',
+ class => 'text',
+ name => 'url',
+ value => $self->{site_url}
+ . "/json/"
+ . $gamename
+ . "/"
+ . ( $self->to_ipv4_str($info->{ip}) // "0.0.0.0" )
+ . ":"
+ . ($info->{hostport} // 0);
+ end;
+ end;
+ Tr;
+ td "Forum Link";
+ td;
+ textarea type => 'textarea',
+ class => 'text',
+ rows => 3,
+ name => 'paste';
+ txt "\[url=$url\]";lit "\n";
+ txt $info->{hostname} // "[unnamed $gamename server]";
+ lit "\n";
+ txt "\[/url\]";
+ end;
+ end;
+ end;
+ Tr;
+ td "HTML Code";
+ td;
+ textarea type => 'textarea',
+ class => 'text',
+ rows => 3,
+ name => 'paste';
+ txt "<a href=\"$url\">";
+ lit "\n";
+ txt $info->{hostname} // "[unnamed $gamename server]";
+ lit "\n";
+ txt "</a>";
+ end;
+ end;
+ end;
+ end; # share options
+
+ end; # mainbox details
+ $self->htmlFooter;
+}
+
+1;