Skip to content

Instantly share code, notes, and snippets.

@Lixivial
Created November 17, 2008 05:12
Show Gist options
  • Save Lixivial/25665 to your computer and use it in GitHub Desktop.
Save Lixivial/25665 to your computer and use it in GitHub Desktop.
Preliminary Infobot qstat wrapper
qstat: Usage for '## <params>':
qstat: ----- __Aliasing__
qstat: -a - Add an alias
qstat: -c - Check an alias
qstat: -e - Edit an alias
qstat: -r - Remove an alias
qstat: ----- __Querying__
qstat: -h - List of hosts to report/query from.
qstat: -p - Player name to search for in output of -c or -h
qstat: -g - Game type to use to query.
qstat: -l - Limit to n results.
qstat: ----- __Miscellaneous__
qstat: -m - Message a user.
qstat: -s - Sort the results by a particular field.
qstat: -d - Sends various debugging messages to the infobot console.
qstat: See 'help ## <param>' for more info on a particular param.
qstat: End of help.
qstat -a: D: Alias a set of hosts to a single parameter to be used with -c.
qstat -a: D: The gameType can be optionally be mapped by appending |gameType to the alias.
qstat -a: U: ## aliasName=host:port,host:port,...,hostn:port|gameType
qstat -a: E: ## bcs=optimalclan.com:26001,optimalclan.com:26002,optimalclan.com:26003,optimalclan.com:26004
qstat -a: E: ## master=dpmaster.deathmask.net:27950|nexuizm
qstat -c: D: Check an aliased list of servers. Combine with -a/-e to check an aliasName's existence.
qstat -c: U: ## aliasName
qstat -c: E: ## master -p [prae]
qstat -c: E: ## aliasName -a
qstat -e: D: Edit an existing alias.
qstat -e: D: The gameType can be optionally be mapped by appending |gameType to the alias.
qstat -e: U: ## aliasName=host:port,host:port,...,hostn:port|gameType
qstat -e: E: ## bcs=optimalclan.com:26001,optimalclan.com:26002
qstat -e: E: ## galts=mshade.org:26000,mshade.org:26002|nexuizs
qstat -r: D: Remove an existing alias.
qstat -r: U: ## aliasName
qstat -r: E: ## bcs
qstat -h: D: Host(s) to query
qstat -h: U: ## host:port,host:port,...,hostn:port
qstat -h: E: qstat -g nexuizm -h dpmaster.deathmask.net:27950 -p [prae]
qstat -h: E: qstat -c bcs -h mshade.org:26000 -p [prae]
qstat -h: E: ## optimalclan.com:26001
qstat -p: D: Player name to search for within output. (Case insensitive)
qstat -p: U: ## playerSearch
qstat -p: E: qstat -g nexuizm -c masters -p [prae]
qstat -g: D: Game type for the host being queried. If omitted, defaults to 'nexuizs'
qstat -g: U: ## gameType
qstat -g: E: ## warsowm -c masters -p [BOT]
qstat -g: E: ## a2s -h 64.27.13.34:27015 -p [woot]
qstat -l: D: Limit output by n lines.
qstat -l: U: ## #
qstat -l: E: qstat -p [prae] -c bcs -l 10
qstat -m: D: Redirect message to user. If -m isn't specified, PM's requestor, and if -m is blank, tells the channel.
qstat -m: U: ## userName (userName needn't be specified)
qstat -m: E: qstat -p [prae] -c bcs -m Lixivial -l 10
qstat -m: E: qstat -h mshade.org:26001 -m
qstat -s: D: Sort output by a given field. (Values are: frags, ping, name, team)
qstat -s: U: ## sortField
qstat -s: E: qstat -c bcs -s frags
qstat -d: D: Print debugging information to the infobot console.
qstat -d: U: ##
# qstat.pl: qstat wrapper
# Author: Lixivial (Jesse M. Pearson)
# Contact: jesall@gmail.com
# Version: v0.5 (20090501).
# Created: 20081116
# NOTE: There are none, other than this is quite possibly the worst thing you'll ever read.
# Possible TODO: Allow for multiple -m's (to message an entire distribution list)
# Related: Aliasing for messaging?
# TODOs:
# + Port this to mozbot.
# + Port this to rbot.
# + Create a notification daemon to perform the following functions:
# * Watch a server, or servers (with all the limiting in hand) for a short period of time
# * NOTE: This should be limited to sane limits, say, 30 minutes, and a minimum of something like
# a minute between notifications.
# * Notify users of subscribed "watched usernames" across n servers and gametypes.
#
#
# Darkplaces-related colour tables/logic copyright (c) 2008 Rudolf "divVerent" Polzer
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
package qstat;
use Switch;
my %paramsMap;
my $aliasFile = $::bot_data_dir."infobot.qstat";
my @text_qfont_table = ( # ripped from DP console.c qfont_table
"\0", '#', '#', '#', '#', '.', '#', '#',
'#', 9, 10, '#', ' ', 13, '.', '.',
'[', ']', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', '.', '<', '=', '>',
' ', '!', '"', '#', '$', '%', '&', '\'',
'(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', ':', ';', '<', '=', '>', '?',
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '{', '|', '}', '~', '<',
'<', '=', '>', '#', '#', '.', '#', '#',
'#', '#', ' ', '#', ' ', '>', '.', '.',
'[', ']', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', '.', '<', '=', '>',
' ', '!', '"', '#', '$', '%', '&', '\'',
'(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', ':', ';', '<', '=', '>', '?',
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '{', '|', '}', '~', '<'
);
my @color_irc2dp_table = (7, 0, 4, 2, 1, 1, 6, 1, 3, 2, 5, 5, 4, 6, 7, 7);
my @color_dp2irc_table = (-1, 4, 9, 8, 12, 11, 13, -1, -1, -1); # not accurate, but legible
my @color_dp2ansi_table = ("m", "1;31m", "1;32m", "1;33m", "1;34m", "1;36m", "1;35m", "m", "1m", "1m"); # not accurate, but legible
my %color_team2dp_table = (5 => 1, 14 => 4, 13 => 3, 10 => 6);
my %color_team2irc_table = (5 => 4, 14 => 12, 13 => 8, 10 => 13);
# Main method.
sub qstat::qstat {
my ( $result, $string, $server, $fileName, $rowCount, $columnCount, $sanitisedName, $sanitisedSearch, @messageArray);
# Setup the parameter map.
&tokenizeParams(shift);
# Enforce field requirements and their relationships.
&populateRequiredFields();
# Call the alias-related checking, which handles the relevant parameter checking.
# Will return a value if we need to halt execution.
if (exists $paramsMap{'checkAlias'} || exists $paramsMap{'addAlias'} || exists $paramsMap{'editAlias'} || exists $paramsMap{'removeAlias'}) {
if (&checkAliases() ne "") {
&debug("qstat-> Aliasing in effect, halting execution.");
return "";
}
}
# Populate the output templates.
$fileName = &generateTemplates();
# Debug some crucial items.
&debug("qstat-> gameType: ${paramsMap{'gameType'}}");
&debug("qstat-> host: ${paramsMap{'host'}}");
&debug("qstat-> fileName: ${fileName}");
&debug("qstat-> blockPlayers: $paramsMap{'blockPlayers'}");
# Support for comma delimited server lists
for ($i = 0; $i< @ { ${paramsMap}->{'host'} }; $i++) {
&debug("qstat-> splitHost ${i}: ".${paramsMap}->{'host'}[$i]);
# Execute the parameterised qstat.
# TODO: Parameterise the binary path such that all known instances are taken into account qstat, quakestat, qstat.exe (CAREFUL: DO NOT OPEN A SECURITY HOLE HERE)
$server = `qstat -Ts ${fileName}Ts.txt -Tp ${fileName}Tp.txt -carets -retry 1 -maxsim 25 $paramsMap{'blockPlayers'} ${paramsMap{'sortType'}} -cn -${paramsMap{'gameType'}} @{ ${paramsMap}->{'host'} }[${i}]`;
# Parse the output, and build the result.
COLUMN: while ($server =~ /(.*)\n/gx) {
$string = "";
$rowCount++;
$columnCount = 0;
# Conduct player searching.
if ($paramsMap{'searchPlayer'} ne "") {
# Split the output fields
for (split('___', $1)) {
# Only perform the following garbage on the name column.
# Or, if they're searching with -b, then search server names.
if (($columnCount == 1 && $paramsMap{'blockPlayers'} eq "-P") || ($columnCount == 4 && $paramsMap{'blockPlayers'} eq "")) {
# TODO: Allow for multiple name searching.
# Sanitise variables to make comparison fair and get rid of bloody special chars (@, [, ], \, *)
$sanitisedName = lc(&sanitizeInput(color_dp2none($_)));
$sanitisedSearch = lc(&sanitizeInput(color_dp2none($paramsMap{'searchPlayer'})));
&debug("qstat-> We're past the 1st row and should be parsing playername as $sanitisedName");
# Perform a generalised query using standardised query syntax (prae* or prae?)
# TODO: Open this up to more perl regexes.
if (rindex($string, '?') > -1 || rindex($string, '*') > -1) {
if ($_ =~ $paramsMap{'searchPlayer'}) {
$string .= "\t ".&checkColours($_);
} else {
$string = "";
next COLUMN;
}
} else {
# If we don't have any query params in the -p parameter, then just do a standard
# rindex against the sanitised inputs.
if (rindex($sanitisedName, $sanitisedSearch) > -1) {
&debug("qstat-> We've rindex'd $_ to $paramsMap{'searchPlayer'}!");
$string .= "\t ".&checkColours($_);
} else {
$string = "";
next COLUMN;
}
}
} else {
# Everything else is standard fare.
# Strip back to standard colourisation.
$string .= "\t\017$_";
}
$columnCount++;
}
$result .= $string;
push(@messageArray, $string);
} else {
# Split the output fields
for (split('___', $1)) {
# Only colour the name column.
if ($columnCount == 1) {
$string .= "\t ".&checkColours($_);
} else {
# Strip back to standard colourisation.
$string .= "\t\017 $_";
}
if ($columnCount >= 2) {
$columnCount = 0;
} else {
$columnCount++;
}
}
$string .= "\n";
push(@messageArray, $string);
$result .= $string;
}
}
}
# Inform the user that nothing was found.
if ($result eq "") {
&debug("qstat-> result: $result");
push(@messageArray, "No results.");
} else {
# If the output limit is greater than the actual result set size, just use the result set size.
if ($paramsMap{'outputLimit'} > @messageArray || $paramsMap{'outputLimit'} == 0) {
$paramsMap{'outputLimit'} = @messageArray;
&debug("qstat-> outputLimit: $paramsMap{'outputLimit'}");
}
unshift(@messageArray, "Here are ".$::who."'s ".($paramsMap{'outputLimit'} ne "" ? $paramsMap{'outputLimit'}." results of ".(@messageArray) : (@messageArray-1)." results").($paramsMap{'searchPlayer'} ne "" ? " for ".&checkColours($paramsMap{'searchPlayer'}."\017") : "").": ");
}
# Reverse the array so that output comes out in the right order due to using pop().
@messageArray = reverse(@messageArray);
$messageCount = $paramsMap{'outputLimit'} ne "" ? $paramsMap{'outputLimit'} : @messageArray;
&debug("qstat-> Our message array is: @messageArray");
&debug("qstat-> Our message count is: $messageCount");
# Tell the channel that they won't be spammed, if they're trying to spam.
if (($messageCount > 10 && $paramsMap{'publicMessage'} ne '' && $paramsMap{'publicMessage'} eq "true") && $paramsMap{'override'} eq "false") {
&performMessage("Output is greater than 10 entries, $::who, PMing you the results so we don't bother these fine folks.");
$paramsMap{'publicMessage'} = $::who;
}
# Loop and perform the messages. This allows us to limit the output, redirect the output, if too long, etc.
for ($i = 0; $i<=$messageCount; $i++) {
if ($messageCount >= 10 && $i > 5) {
&performMessage(pop(@messageArray));
sleep(2);
} else {
&performMessage(pop(@messageArray));
}
}
# Clean up.
unlink("${fileName}Tp.txt");
unlink("${fileName}Ts.txt");
return $result;
}
# Tokenize parameters to accommodate indifference to order
sub qstat::tokenizeParams {
# Split the parameters on their hyphen.
my @params = split(/(^-)|(\s+-)/, $_[0]);
my $paramCount = @params;
# Loop over the passed in parameters and place them in the map.
# NOTE: $i starts at 1 due to extemporaneous output coming back from the hyphen split.
for ($i = 1; $i<$paramCount; $i++)
{
switch (substr($params[$i], 0, 1)) {
# If hostname is specified, put in the scalar, which could simply be a single entity.
case "h" { &debug("qstat-> proposed host: ".substr($params[$i], 2, length $params[$i])); ${paramsMap}->{'host'} = [ split(/,/,&trim(substr($params[$i], 2, length $params[$i]))) ]; }
case "g" { &debug("qstat-> proposed gameType: ".substr($params[$i], 2, length $params[$i])); $paramsMap{'gameType'} = &trim(substr($params[$i], 2, length $params[$i])); }
case "p" { &debug("qstat-> proposed searchPlayer: ".color_dp2none(substr($params[$i], 2, length $params[$i]))); $paramsMap{'searchPlayer'} = &trim(substr($params[$i], 2, length $params[$i])); }
case "l" { &debug("qstat-> proposed outputLimit: ".substr($params[$i], 2, length $params[$i])); $paramsMap{'outputLimit'} = &trim(substr($params[$i], 2, length $params[$i])); }
case "d" { &debug("qstat-> proposed debug: true "); $paramsMap{'debug'} = "true"; }
case "o" { &debug("qstat-> proposed override: true "); $paramsMap{'override'} = "true"; }
case "b" { &debug("qstat-> proposed blockPlayers: true "); $paramsMap{'blockPlayers'} = "true"; }
# If messaging, place in the name, otherwise place in true so that we know to message the channel (blank -m param; not the same as -m missing)
case "m" {
if (substr($params[$i], 2, length $params[$i]) eq "") {
$paramsMap{'publicMessage'} = "true";
} else {
$paramsMap{'publicMessage'} = &trim(substr($params[$i], 2, length $params[$i]));
}
&debug("qstat-> proposed publicMessage: $paramsMap{'publicMessage'}");
}
case "a" {
if (substr($params[$i], 2, length $params[$i]) eq "") {
$paramsMap{'addAlias'} = "true";
} else {
$paramsMap{'addAlias'} = &trim(substr($params[$i], 2, length $params[$i]));
}
&debug("qstat-> proposed addAlias: $paramsMap{'addAlias'}");
}
case "e" {
if (substr($params[$i], 2, length $params[$i]) eq "") {
$paramsMap{'editAlias'} = "true";
} else {
$paramsMap{'editAlias'} = &trim(substr($params[$i], 2, length $params[$i]));
}
&debug("qstat-> proposed addAlias: $paramsMap{'addAlias'}");
}
case "r" {
if (substr($params[$i], 2, length $params[$i]) eq "") {
$paramsMap{'removeAlias'} = "true";
} else {
$paramsMap{'removeAlias'} = &trim(substr($params[$i], 2, length $params[$i]));
}
&debug("qstat-> proposed addAlias: $paramsMap{'addAlias'}");
}
case "s" { &debug("qstat-> proposed sortType: ".substr($params[$i], 2, length $params[$i])); $paramsMap{'sortType'} = &trim(substr($params[$i], 2, length $params[$i])); }
case "c" { &debug("qstat-> proposed checkAlias: ".substr($params[$i], 2, length $params[$i])); $paramsMap{'checkAlias'} = &trim(substr($params[$i], 2, length $params[$i])); }
else {}
}
}
}
# Trims trailing spaces
sub qstat::trim {
my ($returnVar) = @_;
$returnVar =~ s/\s+$//;
return $returnVar;
}
# Returns an escaped version of the input.
sub qstat::sanitizeInput {
my ($returnVar) = @_;
if ($returnVar =~ /([\[\$\#\@\\\]])/gx)
{
$returnVar =~ s/([\[\$\#\@\\\]])/\\$1/g ;
}
&debug("qstat-> escaped variable: $returnVar");
return $returnVar;
}
# Enforces required fields (gameType, etc) to have defaults
# Also checks field dependencies (if searching, don't use host, etc)
# TODO: Player searching, master server translation, etc, etc, etc, etc.
# TODO: Use the master list at http://d3.jpn.org/wiki/GyaASE:Master_Servers to grab a default list for mservers.
# Code stub.
sub qstat::populateRequiredFields {
# Set a default game type.
if ($paramsMap{'gameType'} eq "") {
$paramsMap{'gameType'} = "nexuizs";
} elsif (rindex($paramsMap{'gameType'}, 'nexuiz') == -1) {
# Append ,10 as per suggestions @ http://d3.jpn.org/wiki/GyaASE:Master_Servers
$paramsMap{'gameType'} .= ",10";
}
# If override has not been specified, give it a default value.
if ($paramsMap{'override'} eq "") {
$paramsMap{'override'} = "false";
}
# Default the output limit to 10. Accommodate all and convert it to a integer.
if ($paramsMap{'outputLimit'} eq "") {
$paramsMap{'outputLimit'} = 10;
} elsif ($paramsMap{'outputLimit'} eq "all") {
$paramsMap{'outputLimit'} = 0;
}
# If they left off -b, then use -P.
if ($paramsMap{'blockPlayers'} ne "true") {
$paramsMap{'blockPlayers'} = "-P";
} else {
$paramsMap{'blockPlayers'} = "";
}
# Allow usage of -h and -g
$result = rindex($paramsMap{'editAlias'}, '=');
if ($result == -1 && $paramsMap{'editAlias'} ne "") {
&debug("qstat-> editAlias is only a single param");
if (${paramsMap}->{'host'} ne "") {
$result = rindex(${paramsMap}->{'host'}[0], '|');
&debug(${paramsMap}->{'host'}[0]);
$paramsMap{'editAlias'} .= "=".${paramsMap}->{'host'}[0];
if ($result == -1) {
&debug("qstat-> host did not contain '|'");
$paramsMap{'editAlias'} .= "|".$paramsMap{'gameType'};
}
}
} elsif (exists $paramsMap{'editAlias'} && rindex($paramsMap{'editAlias'}, '|') == -1){
$paramsMap{'editAlias'} .= "|".$paramsMap{'gameType'};
}
# Allow usage of -h and -g
$result = rindex($paramsMap{'addAlias'}, '=');
# Only perform this if they're not checking an alias and if they've actually defined an alias name without an "="
if ($result == -1 && ($paramsMap{'checkAlias'} ne "true" && $paramsMap{'addAlias'} ne "true" && exists $paramsMap{'addAlias'})) {
&debug("qstat-> addAlias is only a single param");
if (exists ${paramsMap}->{'host'}) {
$result = rindex(${paramsMap}->{'host'}[0], '|');
$paramsMap{'addAlias'} .= "=".${paramsMap}->{'host'}[0];
if ($result == -1) {
&debug("qstat-> host did not contain '|'");
$paramsMap{'addAlias'} .= "|".$paramsMap{'gameType'};
}
}
} elsif (exists $paramsMap{'addAlias'} && rindex($paramsMap{'addAlias'}, '|') == -1){
$paramsMap{'addAlias'} .= "|".$paramsMap{'gameType'};
}
&debug("qstat-> editAlias rindex: ".(rindex($paramsMap{'editAlias'}, '=')));
&debug("qstat-> addAlias rindex: ".(rindex($paramsMap{'addAlias'}, '=')));
# Map the sortType to a valid qstat parameter.
$sortType = lc(&trim($paramsMap{'sortType'}));
if ($sortType eq "ping") {
$paramsMap{'sortType'} = "-sort P";
} elsif ($sortType eq "frags") {
$paramsMap{'sortType'} = "-sort F";
} elsif ($sortType eq "team") {
$paramsMap{'sortType'} = "-sort T";
} elsif ($sortType eq "name") {
$paramsMap{'sortType'} = "-sort N";
} elsif ($sortType eq "serverping") {
$paramsMap{'sortType'} = "-sort p";
} elsif ($sortType eq "servergame") {
$paramsMap{'sortType'} = "-sort g";
} elsif ($sortType eq "serverip") {
$paramsMap{'sortType'} = "-sort i";
} elsif ($sortType eq "serverhost") {
$paramsMap{'sortType'} = "-sort h";
} elsif ($sortType eq "serverplayers") {
$paramsMap{'sortType'} = "-sort n";
} else {
$paramsMap{'sortType'} = "";
}
#if ($paramsMap{'addAlias'} ne "" && $paramsMap{'addAlias'} ne "true") {
#}
}
# Wrap messaging so that we can call one standard method.
sub qstat::performMessage {
# Do we msg the channel, or do we private message the peep?
if ($paramsMap{'publicMessage'} ne "") {
if ($paramsMap{'publicMessage'} eq "true") {
&debug("qstat-> Channel: $::talkchannel, Message: @_");
&::msg($::talkchannel, @_);
} else {
&debug("qstat-> -m'd person: $paramsMap{'publicMessage'}, Message: @_");
&::msg($paramsMap{'publicMessage'}, @_);
}
} else {
&debug("qstat-> Requestor: $::who, Message: @_");
&::msg($::who, @_);
}
}
# Dynamically manipulates qstat's template
# Hopefully this method can be removed such that we can reliably use -raw '___' to get output.
sub qstat::generateTemplates {
# Perform a poor man's hash so that multiple operations don't obliterate the generated file.
my $hashFileName = rand();
my $serverTemplate = "$::param{'tempDir'}/${hashFileName}Ts.txt";
my $playerTemplate = "$::param{'tempDir'}/${hashFileName}Tp.txt";
&debug("qstat-> hashFileName: ${hashFileName}");
&debug("qstat-> serverTemplate: ${serverTemplate}");
&debug("qstat-> playerTemplate: ${playerTemplate}");
# Open the files.
if ( !open(OUTS, ">$serverTemplate")) {
&::ERROR("qstat-> cannot open ${serverTemplate}");
}
if ( !open(OUTP, ">$playerTemplate")) {
&::ERROR("qstat-> cannot open ${playerTemplate}");
}
# Perform some special formatting per the type of stuff we're doing.
# Currently only searchPlayer is a special case (so that hostname and user are on same line).
#if ($paramsMap{'searchPlayer'} ne "" && $paramsMap{'gameType'} eq 'nexuizs') {
# print OUTS "\$PLAYERTEMPLATE";
# print OUTP "\$HOSTNAME:\$PORT___\$PLAYERNAME___\$PLAYERPING___\$FRAGS\n";
#} elsif ($paramsMap{'searchPlayer'} ne "") {
if ($paramsMap{'searchPlayer'} ne "" && $paramsMap{'blockPlayers'} eq "-P") {
print OUTS "\$PLAYERTEMPLATE";
print OUTP "\$HOSTNAME___\$PLAYERNAME___\$PLAYERPING___\$FRAGS\n";
} else {
print OUTS "\$PING___\$PLAYERS/\$MAXPLAYERS___\$MAP___\$HOSTNAME___\$SERVERNAME___\n\$PLAYERTEMPLATE";
print OUTP "\$PLAYERPING___\$PLAYERNAME___\$FRAGS\n";
}
# Close the files.
close OUTS;
close OUTP;
return "$::param{'tempDir'}/$hashFileName";
}
# Shall we debug? Woot.
sub qstat::debug {
if ($paramsMap{'debug'} eq "true") {
&::status(@_);
}
}
# Handles reading aliases from the persistent alias file.
sub qstat::readAliases {
my ($count, $recordCount) = 0;
my $found = "false";
# Try opening the file for reading.
if ( !open ROFILE, $aliasFile ) {
# Try creating the file.
if ( !open(ROFILE, ">$aliasFile")) {
&::ERROR("qstat-> cannot open ${aliasFile}");
return "";
}
}
# Read in, line-by-line.
READ: while (<ROFILE>) {
$recordCount++;
$count = 0;
# Retain the line item we're parsing.
$lineItem = $_;
# Skip if it's commented.
next if /^$/;
next if /^#/;
# Don't do it if we've found the record, because we shouldn't be allowed to have duplicate keys.
if ($found eq "false") {
for (split('=', $lineItem)) {
if($_ ne $paramsMap{'checkAlias'} && $count == 0 && $found eq "false") {
next READ;
} elsif ($found eq "true") {
# TODO: Add a check in here to split the value string on | to allow aliasing gameType.
&debug("qstat-> Found the alias $paramsMap{'checkAlias'}. ");
# Split the alias on | in case they've attached a gameType.
@tempArray = split(/\|/,&trim($_));
# Place the array into the paramsMap for host.
${paramsMap}->{'host'} = [ split(/,/,&trim($tempArray[0])) ];
# This is to accommodate special characters in the |gameType addition.
if (split('=', $lineItem) > 1) {
@tempArray = split(/\|/,$lineItem);
&debug("qstat-> tempArray[0]: $tempArray[0]");
}
# If they have attached a gameType set them up.
if ($tempArray[1] ne '' && $paramsMap{'editAlias'} eq "") {
&debug("qstat-> tempArray[1]: $tempArray[1]");
$paramsMap{'gameType'} = &trim($tempArray[1]);
}
# Skip to the last record, as we're done reading the file.
last READ;
} elsif ($_ eq $paramsMap{'checkAlias'} && $count == 0) {
# Set the flag so the next pass will parse the alias value.
$found = "true";
}
$count++;
}
}
}
close ROFILE;
return $found;
}
# Writes aliases to the storage file.
sub qstat::writeAliases {
my $count = 1;
# Open the file for reading if we're editing. Note that we should pass a param
# into &readAliases() to handle this in the future. For now, this hack'll suffice.
if (($paramsMap{'editAlias'} ne "" || $paramsMap{'removeAlias'} ne "true") && (exists $paramsMap{'editAlias'} || exists $paramsMap{'removeAlias'})) {
# Setup a new temp file.
$aliasFileNew = $aliasFile.".tmp.".rand();
if (!open(ROFILE, "$aliasFile")) {
&::ERROR("qstat-> cannot open ${aliasFile}");
return "";
}
# Try creating to the file.
if ( !open(RWFILE, "> $aliasFileNew")) {
&::ERROR("qstat-> cannot open ${aliasFileNew}");
return "";
}
while(<ROFILE>) {
if (m/^$paramsMap{'checkAlias'}/) {
$serverCount = split(/,/,&trim(@{ [ split('=',$paramsMap{'editAlias'}) ] }[1]));
if (!exists $paramsMap{'removeAlias'}) {
$newServerList = @{ [ split(/\|/,&trim(@{ [ split('=',$paramsMap{'editAlias'}) ] }[1])) ] }[0];
$currentServerList = @{ [ split(/\|/,&trim(@{ [ split('=',$_) ] }[1])) ] }[0].','.$newServerList;
$paramsMap{'editAlias'} = $paramsMap{'checkAlias'}."=".$currentServerList."|".$paramsMap{'gameType'}."\n";
&performMessage("Appended ".$serverCount." server".($serverCount == 1 ? "" : "s").($newServerList ne "" ? " and changed game type to ".$paramsMap{'gameType'} : "")." to $paramsMap{'checkAlias'}, $::who.");
} elsif ($paramsMap{'removeAlias'} eq "true") {
$paramsMap{'editAlias'} .= "\n";
&performMessage("Replaced ".$paramsMap{'checkAlias'}." with ".$serverCount." server".($serverCount == 1 ? "" : "s")." and game type ".$paramsMap{'gameType'}.", $::who.");
} elsif (exists $paramsMap{'removeAlias'} && $paramsMap{'removeAlias'} ne "true") {
$paramsMap{'editAlias'} = "";
&performMessage("The alias $paramsMap{'checkAlias'} was removed, $::who.");
}
print RWFILE $paramsMap{'editAlias'};
} else {
print RWFILE $_;
}
}
close ROFILE;
close RWFILE;
unlink($aliasFile);
rename($aliasFileNew, $aliasFile);
return "";
} else {
# Try appending to the file.
if ( !open(RWFILE, ">>$aliasFile")) {
# Try creating to the file.
if ( !open(RWFILE, ">$aliasFile")) {
&::ERROR("qstat-> cannot open ${aliasFile}");
return "";
}
}
}
if ($paramsMap{'editAlias'} eq "") {
# Write the suggested alias.
print RWFILE $paramsMap{'addAlias'}."\n";
&debug("qstat-> Added $paramsMap{'addAlias'} to $aliasFile by $::who");
}
# Close the file.
close RWFILE;
# Break apart the serverlist that they added.
@serverList = split(/,/,&trim(@{ [ split('=',$paramsMap{'addAlias'}) ] }[1]));
for (split('=', $paramsMap{'addAlias'})) {
if($count == 1) {
# First time through, tell them they've aliased # of servers.
&debug(@{ [ split(/\|/,&trim(@{ [ split('=',$paramsMap{'addAlias'}) ] }[1])) ] }[1]);
&performMessage("Aliased ".@serverList." server".(@serverList == 1 ? "" : "s").(@{ [ split(/\|/,&trim(@{ [ split('=',$paramsMap{'addAlias'}) ] }[1])) ] }[1] ne "" ? " of game type ".@{ [ split(/\|/,&trim(@{ [ split('=',$paramsMap{'addAlias'}) ] }[1])) ] }[1] : "")." to $_, $::who.");
# Deletion. This is just annoying.
#} else {
# # Tell them the servers they aliased.
# for ($i = 0; $i<@serverList; $i++) {
# &performMessage($serverList[$i]);
# }
}
$count++;
}
}
# Performs relevant alias related routines.
# Will return a value if something is supposed to break main execution.
sub qstat::checkAliases {
# If they're trying to use an alias to query, check for its existence.
if ($paramsMap{'checkAlias'} ne "" && $paramsMap{'addAlias'} eq "" && !exists $paramsMap{'removeAlias'} && !exists $paramsMap{'editAlias'}) {
# Let them know if it wasn't found and break execution.
if (&readAliases() eq "false") {
&debug("qstat-> The alias $paramsMap{'checkAlias'} was not found.");
&performMessage("The alias $paramsMap{'checkAlias'} was not found, $::who");
return "break";
}
# If they're trying to check for an alias's existence, tell them the relevant status.
} elsif ($paramsMap{'checkAlias'} ne "" && $paramsMap{'addAlias'} ne "" && !exists $paramsMap{'removeAlias'} && !exists $paramsMap{'editAlias'} && exists $paramsMap{'addAlias'} && exists $paramsMap{'checkAlias'}) {
if (&readAliases() eq "true") {
&performMessage("The alias $paramsMap{'checkAlias'} has already been defined, $::who. You may edit it with -e $paramsMap{'checkAlias'}=host1:port,host2:port,hostn:port.");
} else {
&performMessage("The alias $paramsMap{'checkAlias'} is available, $::who. Please see help qstat -e for more information on editing/removing it.");
}
return "break";
# If they're trying to add an alias, check the relevant alias first, then do the corresponding action.
} elsif ($paramsMap{'addAlias'} ne "" && $paramsMap{'addAlias'} ne "true" && !exists $paramsMap{'removeAlias'} && !exists $paramsMap{'editAlias'}) {
$paramsMap{'checkAlias'} = @{ [ split('=',$paramsMap{'addAlias'}) ] }[0];
&debug("qstat-> checkAlias: $paramsMap{'checkAlias'}");
&debug("qstat-> readAliases(): ".&readAliases());
if (&readAliases() eq "true") {
&performMessage("The alias $paramsMap{'checkAlias'} has already been defined, $::who. Please see help qstat -e for more information on editing/removing it.");
} else {
&writeAliases();
}
return "break";
# If they specified nothing for adding.
} elsif (exists $paramsMap{'removeAlias'} && $paramsMap{'removeAlias'} ne "true") {
$paramsMap{'checkAlias'} = $paramsMap{'removeAlias'};
$paramsMap{'editAlias'} = "\n";
if (&readAliases() eq "true") {
&writeAliases();
} else {
&performMessage("The alias $paramsMap{'checkAlias'} was not found, and so it could not be replaced, $::who.");
}
return "break";
# If they want to replace the alias.
} elsif ($paramsMap{'editAlias'} ne "" && $paramsMap{'removeAlias'} eq "true") {
$paramsMap{'checkAlias'} = @{ [ split('=',$paramsMap{'editAlias'}) ] }[0];
if (&readAliases() eq "true") {
&writeAliases();
} else {
$paramsMap{'addAlias'} = $paramsMap{'editAlias'};
&writeAliases();
}
return "break";
# If they want to append something (gameType or a couple of servers) to the alias name.
} elsif (exists $paramsMap{'editAlias'} && $paramsMap{'editAlias'} ne "") {
$paramsMap{'checkAlias'} = @{ [ split('=',$paramsMap{'editAlias'}) ] }[0];
if (&readAliases() eq "true") {
&writeAliases();
} else {
# Set the addAlias to be editAlias and clear the edit alias so that
# standard writing can move forward.
$paramsMap{'addAlias'} = $paramsMap{'editAlias'};
$paramsMap{'editAlias'} = "";
&performMessage("The alias $paramsMap{'checkAlias'} was not found, $::who. Creating a new one for you.");
&writeAliases();
}
return "break";
}
}
# For darkplaces, based on div0's colour parsing table.
# Generic wrapper. This should be expanded to accommodate other common game types.
sub qstat::checkColours {
# Todo: Map out common gametypes, like a2s, etc, to handle those colours.
my ($message) = @_;
if ($paramsMap{'gameType'} eq 'nexuizs' || $paramsMap{'gameType'} eq 'nexuizm') {
return color_dp2irc($message);
} else {
return $message;
}
}
# Ripped straight from divVerent's excellent rcon2irc script.
sub color_dp_transform(&$)
{
my ($block, $message) = @_;
$message =~ s{(?:(\^\^)|\^x([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])|\^([0-9])|(.))(?=([0-9,]?))}{
defined $1 ? $block->(char => '^', $7) :
defined $2 ? $block->(rgb => [hex $2, hex $3, hex $4], $7) :
defined $5 ? $block->(color => $5, $7) :
defined $6 ? $block->(char => $6, $7) :
die "Invalid match";
}esg;
return $message;
}
sub color_dp2irc($)
{
my ($message) = @_;
my $color = -1;
return color_dp_transform
{
my ($type, $data, $next) = @_;
if($type eq 'rgb')
{
$type = 'color';
$data = color_rgb2basic($data);
}
$type eq 'char' ? $text_qfont_table[ord $data] :
$type eq 'color' ? do {
my $oldcolor = $color;
$color = $color_dp2irc_table[$data];
$color == $oldcolor ? '' :
$color < 0 ? "\017" :
$next eq ',' ? "\003$color\002\002" :
sprintf "\003%02d", $color;
} :
die "Invalid type";
}
$message;
}
sub color_dp2none($)
{
my ($message) = @_;
return color_dp_transform
{
my ($type, $data, $next) = @_;
$type eq 'char'
? $text_qfont_table[ord $data]
: "";
}
$message;
}
sub color_rgb2basic($)
{
my ($data) = @_;
my ($R, $G, $B) = @$data;
my $min = [sort { $a <=> $b } ($R, $G, $B)]->[0];
my $max = [sort { $a <=> $b } ($R, $G, $B)]->[-1];
my $v = $max / 15;
my $s = ($max == $min) ? 0 : 1 - $min/$max;
if($s < 0.2)
{
return 0 if $v < 0.5;
return 7;
}
my $h;
if($max == $min)
{
$h = 0;
}
elsif($max == $R)
{
$h = (60 * ($G - $B) / ($max - $min)) % 360;
}
elsif($max == $G)
{
$h = (60 * ($B - $R) / ($max - $min)) + 120;
}
elsif($max == $B)
{
$h = (60 * ($R - $G) / ($max - $min)) + 240;
}
return 1 if $h < 36;
return 3 if $h < 80;
return 2 if $h < 150;
return 5 if $h < 200;
return 4 if $h < 270;
return 6 if $h < 330;
return 1;
}
sub color_dp_rgb2basic($)
{
my ($message) = @_;
return color_dp_transform
{
my ($type, $data, $next) = @_;
$type eq 'char' ? ($data eq '^' ? '^^' : $data) :
$type eq 'color' ? "^$data" :
$type eq 'rgb' ? "^" . color_rgb2basic $data :
die "Invalid type";
}
$message;
}
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment