6 # Connects to a Quake2 server, queries current map, players, mod
7 # and various other stuff, and builds a web page from that.
8 # Templates are supported.
10 # server: IP or FQDN of server. Defaults to 194.64.167.5
11 # port: Port. Defaults to 27910
12 # template: Template file. Defaults to ndc
14 # Copyright (C) 2001 Andreas Ulbrich, Ralf Ertzinger (ndccode@ndc.sh)
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
37 # Define some variables
41 my $mappath = "/maparchiv"; # Path to map thumbnails
42 my $template_path = "."; # Path to templates.
43 my $mapnamefile = "mapnames.txt";
44 my $scriptpath = "http://www.ndc.sh/cgi-bin/quake/quakec5.pl"; # The script itself when called by HTTP
50 # Info about the script
51 $variables{parser_selfinfo} = "<!--\nThis is the [NDC] serverinfo script, Release 5.\n" .
52 "Featuring support for Quake2, HTML templatefiles and SQL-Queries.\n" .
53 "Parsing-Engine: Lilith, Version 1.1.\n" .
54 "Output is HTML 4.01 strict compliant.\n\n" .
55 "Original Release by [NDC]Maria.\n" .
56 "Quake connection code by [NDC]Maria and [NDC]Sundancer.\n" .
57 "Lilith by [NDC]Sundancer.\n" .
58 "Source code is available. Contact ndccode\@ndc.sh or see http://www.ndc.sh/ndccode.\n" .
63 $variables{server_address} = ($CGIquery->param("server")?$CGIquery->param("server"):"194.64.167.5");
64 $variables{server_port} = ($CGIquery->param("port")?$CGIquery->param("port"):"27910");
65 $sort = ($CGIquery->param("sort")?$CGIquery->param("sort"):0);
66 $template = ($CGIquery->param("template")?$CGIquery->param("template"):"ndc");
69 if (-e $mapnamefile) {
70 open(MAPNAMES,$mapnamefile);
71 while ($line=<MAPNAMES>) {
72 ($maphandle,$mapn) = split(":",$line);
74 $mapname{$maphandle} = $mapn;
79 $quakeresponse = QueryQuakeServer("status");
81 ### Ausgabeformatierung ###
82 ## Abtrennen der ersten Zeile mit den Systeminformationen ###
83 @ausg = split (/\n/,$quakeresponse);
85 ## Zuweisen der Ausgabevariablen zu $variable{wasausgegebenwerdensoll} ##
86 @config = split (/\\/,$ausg[1]);
87 for ( $i = 1; $i < $#config; $i= $i + 2) {
89 $variables{"server_${var}"} = $config[$i + 1];
92 ## Zaehlen der Onlinespieler ##
93 $anzplayer = scalar(@ausg) - 2;
94 $variables{server_actclients} = $anzplayer;
96 #### Umfomartieren der Spielernamen
97 foreach $i (2..($#ausg))
100 @playerdata = ($ausg[$i] =~ /^(\d+)\s(\d+)\s"(.+)"$/);
101 $name = $playerdata[2];
102 $pings{$name} = $playerdata[1];
103 $frags{$name} = $playerdata[0];
104 $nicks{$name} = $name;
105 $nicks{$name} =~ s/&/&/g;
106 $nicks{$name} =~ s/</</g;
107 $nicks{$name} =~ s/>/>/g;
108 $nicks{$name} =~ s/"/"/g;
112 ## Mapnamen auf vollen Namen erweitern (wenn möglich)
113 if (defined($mapname{$variables{server_mapname}})) {
114 $variables{server_map} = $mapname{$variables{server_mapname}} . " (". $variables{server_mapname} . ")";
116 $variables{server_map} = $variables{server_mapname};
119 #### Ermittlung der Load falls moeglich #####
122 $variables{server_load} = $load;
123 $variables{server_load_color} = "<FONT color=\"#000000\">" . $load. "</FONT>";
125 $variables{server_load_color} = "<FONT color=\"#00CC00\">$load</FONT>";
127 if (($load > 0.7) && ($load < 1)) {
128 $variables{server_load_color} = "<FONT color=\"#CCCC00\">$load</FONT>";
131 $variables{server_load_color} = "<FONT color=\"#CC0000\">$load</FONT>";
135 ###### Sortieren #####
137 # By default, sort by frags.
138 @sortkey = sort { $frags{$b} <=> $frags{$a} } (keys %frags);
142 @sortkey = sort { $frags{$b} <=> $frags{$a} } (keys %frags);
147 @sortkey = sort { $pings{$a} <=> $pings{$b} } (keys %pings);
152 @sortkey = sort { $nicks{$a} cmp $nicks{$b} } (keys %nicks);
156 $variables{parser_sortmode} = "Sortiert nach $sortname";
158 #### Ermittlung und formatierung des Datums #####
159 $date_command = "/bin/date";
160 $date = `$date_command +"%T Uhr \ \; %d.%m.%Y"`;
162 $variables{server_date} = $date;
164 #### Bild von Map? #####
166 if (-e "$mappath/$variables{server_mapname}/$variables{server_mapname}_1_thumb.jpg") {
167 $variables{map_thumbnail} = "http://www.ndc.sh/maparchiv/$variables{server_mapname}/$variables{server_mapname}_1_thumb.jpg";
169 $variables{map_thumbnail} = "http://www.ndc.sh/maparchiv/blank.gif";
172 ## Variables for different sorting
173 $variables{parser_selfsortname} = $scriptpath . "?server=" . $variables{server_address} . "&port=" . $variables{server_port} . "&sort=2";
174 $variables{parser_selfsortfrags} = $scriptpath . "?server=" . $variables{server_address} . "&port=" . $variables{server_port} . "&sort=0";
175 $variables{parser_selfsortping} = $scriptpath . "?server=" . $variables{server_address} . "&port=" . $variables{server_port} . "&sort=1";
180 # Define variables used by if and loop statements
182 $in_if_noclients = 0;
183 $in_loop_players = 0;
185 # Check template file
186 if ($eingabe{template} =~ /\W/) {
187 # template file contains illegal characters
188 $template_file = $template_path . "/ndc.template";
190 $template_file = $template_path . "/" . $eingabe{template} . ".template";
193 if ( ! -e $template_file ) {
194 $template_file = $template_path . "/ndc.template";
197 # Start by printing content type
198 print "Content-Type: text/html\n\n";
200 open (TEMPLATE, $template_file) or die "Could not open template file: $!\n";
201 @main_source=<TEMPLATE>;
204 ##### Subroutines ######
209 # Start by printing content type
210 print "Content-Type: text/html\n\n";
211 print "<HTML><BODY>Can\'t connect to $server:$port\n<BR>$!\n</BODY></HTML>";
216 @dmflags_name = ("Health" , "Powerups" , "Weapon Stay" , "Falling Damage" , "Instant Powerups" , "Same Map" , "Teams by Skin" , "Teams by Model" , "Friendly Fire" , "Spawn Farthest" , "Force Respawn" , "Armor" , "Allow Exit" , "Infinite Ammo" , "Quad Drop" , "Fixed FOV");
217 @dmflags_neg = (1 , 2 , 0 , 8 , 0 , 0 , 0 , 0 , 256 , 0 , 0 , 2048 , 0 , 0 , 0 , 0);
220 print "<P>\ \;<P><UL><a name=\"dmflags\"><b>DMFLAGS f\ü\;r DUMMIES<\/b><\/a><BR>\n";
221 print "<TABLE BORDER = \"0\">\n";
222 print "<TR><td WIDTH=\"150\" ALIGN=\"CENTER\" BGCOLOR=\"\#FFFFCC\"><b>Flag<\/B><\/td>\n";
223 print "<td WIDTH=\"60\" ALIGN=\"CENTER\" BGCOLOR=\"\#FFFFCC\"><b>On<\/B><\/td>\n";
224 print "<td WIDTH=\"60\" ALIGN=\"CENTER\" BGCOLOR=\"\#FFFFCC\"><b>Off<\/b><\/td><\/TR>\n";
225 foreach $i (0..($#dmflags_name)) {
226 if ( (($text{dmflags} & (2**$i)) ^ $dmflags_neg[$i]) == (2**$i)) {
227 print "<TR><TD ALIGN=\"LEFT\" BGCOLOR=\"\#CCCCCC\">$dmflags_name[$i]<\/TD>\n";
228 print "<TD ALIGN=\"CENTER\" BGCOLOR=\"\#EEEEEE\"><img src=\"http\:\/\/www.ndc.sh\/graphics\/haken.gif\"><\/TD>\n";
229 print "<TD ALIGN=\"CENTER\" BGCOLOR=\"\#EEEEEE\">\ \;<\/TD><\/TR>\n";
231 print "<TR><TD ALIGN=\"LEFT\" BGCOLOR=\"\#CCCCCC\">$dmflags_name[$i]<\/TD>\n";
232 print "<TD ALIGN=\"CENTER\" BGCOLOR=\"\#EEEEEE\">\ \;<\/TD>\n";
233 print "<TD ALIGN=\"CENTER\" BGCOLOR=\"\#EEEEEE\"><img src=\"http\:\/\/www.ndc.sh\/graphics\/haken.gif\"><\/TD><\/TR>\n";
237 print "<\/TABLE><\/UL>\n";
244 # Takes a player name from subroutine call, returns a URL if player is in
245 # database, undef else.
247 $pl_sql_nick = shift;
248 $pl_sql_nick_old = $pl_sql_nick;
250 $pl_sql_nick =~ s/(\\)/\\$1/g;
251 $pl_sql_nick =~ s/(')/\\$1/g;
255 $sql_database = "stats";
256 $sql_db_table = "PlayerStats";
257 $sql_datasource = "DBI:mysql:$sql_database";
260 $sql_db_handle = DBI->connect($sql_datasource, $sql_username, $sql_password) or return undef;
262 $sql_query = $sql_db_handle->prepare("SELECT nick FROM $sql_db_table WHERE nick='$pl_sql_nick'");
264 $sql_result = $sql_query->fetchrow_hashref;
267 if (defined($sql_result)) {
268 $returnvalue = "\"http\:\/\/www.ndc.sh\/cgi-bin\/quake\/playerstats.pl\?nick\=" . make_compatible($pl_sql_nick_old) . "\"";
270 $returnvalue = undef;
273 $sql_db_handle->disconnect;
278 # This routine takes an array as input, and parses it line by line,
279 # echoing results to the screen.
281 # Switches like if and loop are GLOBAL!
295 LOOP: while ($template_line = shift) {
296 # Catch special requests
297 if ($template_line =~ /^\s*\<!--if [\w_]+--\>/) {
298 ($if_variable) = $template_line =~ /\<!--if ([\w_]+)--\>/;
299 if ($if_variable eq "clients") {
302 if ($if_variable eq "noclients") {
303 $in_if_noclients = 1;
307 if ($template_line =~ /^\s*\<!--endif [\w_]+--\>/) {
308 ($if_variable) = ($template_line =~ /\<!--endif ([\w_]+)--\>/);
309 if ($if_variable eq "clients") {
312 if ($if_variable eq "noclients") {
313 $in_if_noclients = 0;
318 if ($template_line =~ /^\s*\<!--loop [\w_]+--\>/) {
319 ($loop_variable) = ($template_line =~ /\<!--loop ([\w_]+)--\>/);
320 if ($if_variable eq "clients") {
321 $in_loop_clients = 1;
326 if ($template_line =~ /^\s*\<!--endloop [\w_]+--\>/) {
327 ($loop_variable) = ($template_line =~ /\<!--endloop ([\w_]+)--\>/);
328 if ($if_variable eq "clients") {
329 $in_loop_clients = 0;
330 # Now, loop through all clients and parse
331 # the saved loop once for each.
333 foreach $clients ( @sortkey) {
334 $dbURL = GetDBURL($clients);
335 $variables{parser_clientrank} = ++$i;
336 $variables{parser_clientname} = $nicks{$clients};
337 if (defined($dbURL)) {
338 $variables{parser_clientlink} = "<a href=$dbURL>" . $nicks{$clients} . "</a>";
339 $variables{parser_clientlink_color} = "<a href=$dbURL><font color=\"#000000\">" . $nicks{$clients} . "</font></a>";
341 $variables{parser_clientlink} = $nicks{$clients};
342 $variables{parser_clientlink_color} = $nicks{$clients};
344 $variables{parser_clientping} = $pings{$clients};
345 $variables{parser_clientfrags} = $frags{$clients};
353 # Check if we are to ignore these lines
354 if (($in_if_clients == 1) && ($variables{server_actclients} == 0)) {
357 if (($in_if_noclients == 1) && ($variables{server_actclients} != 0)) {
361 # If we are in loop mode, copy lines into an array for
363 if ($in_loop_clients == 1) {
364 push @looparray, $template_line;
368 @parser_comment = ($template_line =~ /\<!--\$([\w_]+)--\>/g);
369 foreach $comment ( @parser_comment) {
370 if (defined($variables{$comment})) {
371 $fullcomment = "\\<!--\\\$" . $comment . "--\\>";
372 $replacement = $variables{$comment};
373 $template_line =~ s/$fullcomment/$replacement/g;
376 print $template_line;
380 sub make_compatible {
384 $input =~ s/\+/%2B/g;
390 $input =~ s/\[/%5B/g;
391 $input =~ s/\]/%5D/g;
392 $input =~ s/\//%2F/g;
393 $input =~ s/\|/%7C/g;
394 $input =~ s/\?/%3F/g;
395 $input =~ s/\x1c/%1C/g;
400 sub QueryQuakeServer {
402 # Takes a command from caller. Executes the command on the server defined by the
403 # global variables $variables{server_address} and $variables{server_port}
404 # Returns a literal containing the server's response.
406 my ($count, $hisiaddr, $host, $iaddr,$paddr, $port ,$proto, $hispaddr, $command, $query, $response);
410 $iaddr = gethostbyname(hostname());
411 $proto = getprotobyname('udp');
412 $paddr =sockaddr_in(0 ,$iaddr);
415 ### Netzwerk und auslesen der Daten ###
417 socket (SOCKET, PF_INET, SOCK_DGRAM, $proto) or &cantconnect;
418 bind(SOCKET, $paddr) or die "bind: $!";
422 $hisiaddr = inet_aton($variables{server_address}) or &cantconnect;
423 $hispaddr = sockaddr_in($variables{server_port}, $hisiaddr);
424 $query = "\xff\xff\xff\xff$command\x00";
425 defined(send(SOCKET, $query, 0, $hispaddr)) or &cantconnect;
427 ## Timeout nach 5 sec ##
429 vec($rin, fileno(SOCKET), 1) = 1;
430 while (($response eq "" ) && select($rout = $rin, undef ,undef ,5.0))
432 ($hispaddr = recv(SOCKET, $response, 1000, 0)) || &cantconnect;