# MISC STRING UTILITY ROUTINES to convert between DarkPlaces and IRC conventions
# convert mIRC color codes to DP color codes
+our $color_utf8_enable = 1;
our @color_irc2dp_table = (7, 0, 4, 2, 1, 1, 6, 1, 3, 2, 5, 5, 4, 6, 7, 7);
our @color_dp2irc_table = (-1, 4, 9, 8, 12, 11, 13, -1, -1, -1); # not accurate, but legible
our @color_dp2ansi_table = ("m", "1;31m", "1;32m", "1;33m", "1;34m", "1;36m", "1;35m", "m", "1m", "1m"); # not accurate, but legible
}
our @text_qfont_table = ( # ripped from DP console.c qfont_table
- "\0", '#', '#', '#', '#', '.', '#', '#',
+ '', '#', '#', '#', '#', '.', '#', '#',
'#', 9, 10, '#', ' ', 13, '.', '.',
'[', ']', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', '.', '<', '=', '>',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '{', '|', '}', '~', '<'
);
+sub text_qfont_table($)
+{
+ my ($char) = @_;
+ my $o = ord $char;
+ if($color_utf8_enable)
+ {
+ return (($o & 0xFF00) == 0xE000) ? $text_qfont_table[$o & 0xFF] : $char;
+ }
+ else
+ {
+ return $text_qfont_table[$o];
+ }
+}
sub text_dp2ascii($)
{
my ($message) = @_;
- $message = join '', map { $text_qfont_table[ord $_] } split //, $message;
+ $message = join '', map { text_qfont_table $_ } split //, $message;
}
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) :
{
my ($type, $data, $next) = @_;
$type eq 'char'
- ? $text_qfont_table[ord $data]
+ ? text_qfont_table $data
: "";
}
$message;
$data = color_rgb2basic $data;
}
- $type eq 'char' ? $text_qfont_table[ord $data] :
+ $type eq 'char' ? text_qfont_table $data :
$type eq 'color' ? do {
my $oldcolor = $color;
$color = $color_dp2irc_table[$data];
$data = color_rgb2basic $data;
}
- $type eq 'char' ? $text_qfont_table[ord $data] :
+ $type eq 'char' ? text_qfont_table $data :
$type eq 'color' ? do {
my $oldcolor = $color;
$color = $color_dp2ansi_table[$data];
# Connection:
# $conn->sockname() returns a connection type specific representation
# string of the local address, or undef if not applicable.
+# $conn->peername() returns a connection type specific representation
+# string of the remote address, or undef if not applicable.
# $conn->send("string") sends something over the connection.
# $conn->recv() receives a string from the connection, or returns "" if no
# data is available.
PeerAddr => $remote,
PeerPort => $defaultport
) or die "socket $proto/$local/$remote/$defaultport: $!";
+ binmode $sock;
$sock->blocking(0);
my $you = {
# Mortal fool! Release me from this wretched tomb! I must be set free
return "@{[inet_ntoa $addr]}:$port";
}
+# $sock->peername() returns the remote address of the socket.
+sub peername($)
+{
+ my ($self) = @_;
+ my ($port, $addr) = sockaddr_in $self->{sock}->peername();
+ return "@{[inet_ntoa $addr]}:$port";
+}
+
# $sock->send($data) sends some data over the socket; on success, 1 is returned.
sub send($$)
{
sub send($$$)
{
my ($self, $line, $nothrottle) = @_;
+ utf8::encode $line
+ if $color_utf8_enable;
if($self->{secure} > 1)
{
$self->{connector}->send("\377\377\377\377getchallenge");
if not defined $s;
length $s
or last;
- if($s =~ /^\377\377\377\377challenge (.*)$/s)
+ if($s =~ /^\377\377\377\377challenge (.*?)(?:$|\0)/s)
{
return $1;
}
my @out = ();
while($self->{recvbuf} =~ s/^(.*?)(?:\r\n?|\n)//)
{
- push @out, $1;
+ my $s = $1;
+ utf8::decode $s
+ if $color_utf8_enable;
+ push @out, $s;
}
return @out;
}
sub send($$$)
{
my ($self, $line, $nothrottle) = @_;
+ utf8::encode $line
+ if $color_utf8_enable;
my $t = time();
if(defined $self->{capacity})
{
my @out = ();
while($self->{recvbuf} =~ s/^(.*?)(?:\r\n?|\n)//)
{
- push @out, $1;
+ my $s = $1;
+ utf8::decode $s
+ if $color_utf8_enable;
+ push @out, $s;
}
return @out;
}
dp_password => undef,
dp_status_delay => 30,
dp_server_from_wan => "",
+ dp_listen_from_server => "",
+ dp_utf8_enable => $color_utf8_enable,
+ dp_timinglog => "",
irc_local => "",
irc_admin_password => "",
irc_admin_quote_re => "",
irc_reconnect_delay => 300,
+ irc_commands => "",
plugins => "",
);
+sub pickip($$)
+{
+ my ($wan, $lan) = @_;
+ # $wan shall override $lan
+ return $lan
+ if not length $wan;
+ return $wan
+ if $wan =~ /:\d+$/; # full override
+ return $wan
+ if $lan !~ /:(\d+)$/;
+ return "$wan:$1";
+}
+
# Xonotic specific parsing of some server messages
my $slots = $store{slots_max} - $store{slots_active};
my $slots_s = ($slots == 1) ? '' : 's';
$slotsstr = " ($slots free slot$slots_s)";
- my $s = $config{dp_server_from_wan} || $config{dp_server};
+ my $s = pickip($config{dp_server_from_wan}, $config{dp_server});
$slotsstr .= "; join now: \002xonotic +connect $s"
if $slots >= 1 and not $store{lms_blocked};
}
die "The following config items are missing: @missing"
if @missing;
+$color_utf8_enable = $config{dp_utf8_enable};
# Create a channel for error messages and other internal status messages...
$channels{irc} = new Channel::Line(new Connection::Socket(tcp => $config{irc_local} => $config{irc_server} => 6667));
$channels{dp} = new Channel::QW(my $dpsock = new Connection::Socket(udp => $config{dp_listen} => $config{dp_server} => 26000), $config{dp_password}, $config{dp_secure}, $config{dp_secure_challengetimeout});
$config{dp_listen} = $dpsock->sockname();
+$config{dp_server} = $dpsock->peername();
print "Listening on $config{dp_listen}\n";
$channels{irc}->throttle(0.5, 5);
return;
# this will keep irc_error_active
}
- $channels{irc} = new Channel::Line(new Connection::Socket(tcp => "" => $config{irc_server} => 6667));
+ $channels{irc} = new Channel::Line(new Connection::Socket(tcp => $config{irc_local} => $config{irc_server} => 6667));
delete $store{$_} for grep { /^irc_/ } keys %store;
$store{irc_nick} = "";
schedule sub {
my ($timer) = @_;
- out dp => 0, 'sv_cmd bans', 'status 1', 'log_dest_udp';
+ out dp => 0, 'sv_cmd banlist', 'status 1', 'log_dest_udp';
$store{status_waiting} = -1;
} => 1;
# this will clear irc_error_active
# we get here again when Q asks us
}
}
+
+ for(split / *; */, $store{irc_commands})
+ {
+ s/\$nick/$store{irc_nick}/g;
+ out irc => 1, $_;
+ }
# if we get here, we are on IRC
$store{irc_joined_channel} = 1;
schedule sub {
+ # wait 1 sec to let stuff calm down
out irc => 1, "JOIN $config{irc_channel}";
} => 1;
return 0;
[ dp => q{"log_dest_udp" is "([^"]*)" \["[^"]*"\]} => sub {
my ($dest) = @_;
my @dests = split ' ', $dest;
- return 0 if grep { $_ eq $config{dp_listen} } @dests;
- out dp => 0, 'log_dest_udp "' . join(" ", @dests, $config{dp_listen}) . '"';
+ return 0 if grep { $_ eq pickip($config{dp_listen_from_server}, $config{dp_listen}) } @dests;
+ out dp => 0, 'log_dest_udp "' . join(" ", @dests, pickip($config{dp_listen_from_server}, $config{dp_listen})) . '"';
return 0;
} ],
[ irc => q{:[^ ]* 001 .*} => sub {
$store{irc_seen_welcome} = 1;
$store{irc_nick} = $store{irc_nick_requested};
+
+ # If users for quakenet are listed, parse them into a hash and schedule a sub to query information
+ if ($config{irc_quakenet_authusers} ne '') {
+ $store{irc_quakenet_users} = { map { $_ => 1 } split / /, $config{irc_quakenet_authusers} };
+
+ schedule sub {
+ my ($timer) = @_;
+ out irc => 0, "PRIVMSG Q :users " . $config{irc_channel};
+ schedule $timer => 300;;
+ } => 1;
+ }
+
return irc_joinstage(0);
} ],
return irc_joinstage(0);
} ],
- # Catch joins of people in a channel the bot is in and catch our own joins of a channel
+ # Catch joins of people in a channel the bot is in and catch our own joins of a channel,
+ # detect channel join message and note hostname length to get the maximum allowed line length
[ irc => q{:(([^! ]*)![^ ]*) JOIN (#.+)} => sub {
my ($hostmask, $nick, $chan) = @_;
+
+ if ($nick eq $store{irc_nick}) {
+ $store{irc_maxlen} = 510 - length($hostmask);
+ if($store{irc_joined_channel} == 1)
+ {
+ $store{irc_joined_channel} = 2;
+ }
+ print "* detected maximum line length for channel messages: $store{irc_maxlen}\n";
+ }
+
return 0 unless ($store{irc_quakenet_users});
- if ($nick eq $config{irc_nick}) {
+ if ($nick eq $store{irc_nick}) {
out irc => 0, "PRIVMSG Q :users $chan"; # get auths for all users
} else {
$store{quakenet_hosts}->{$nick} = $hostmask;
# remove myself from the log destinations and exit everything
[ dp => q{quitting rcon2irc (??{$store{quitcookie}}): log_dest_udp is (.*) *} => sub {
my ($dest) = @_;
- my @dests = grep { $_ ne $config{dp_listen} } split ' ', $dest;
+ my @dests = grep { $_ ne pickip($config{dp_listen_from_server}, $config{dp_listen}) } split ' ', $dest;
out dp => 0, 'log_dest_udp "' . join(" ", @dests) . '"';
exit 0;
return 0;
return 0;
} ],
- # detect channel join message and note hostname length to get the maximum allowed line length
- [ irc => q{(:(?i:(??{$store{irc_nick}}))![^ ]* )(?i:JOIN) :(?i:(??{$config{irc_channel}}))} => sub {
- $store{irc_maxlen} = 510 - length($1);
- $store{irc_joined_channel} = 1;
- print "* detected maximum line length for channel messages: $store{irc_maxlen}\n";
- return 0;
- } ],
-
# chat: Xonotic server -> IRC channel
[ dp => q{\001(.*?)\^7: (.*)} => sub {
my ($nick, $message) = map { color_dp2irc $_ } @_;
my ($all, $cpu, $lost, $avg, $max, $sdev) = @_;
return 0 # don't complain when just on the voting screen
if !$store{playing};
+ if(length $config{dp_timinglog})
+ {
+ open my $fh, '>>', $config{dp_timinglog}
+ or warn "open >> $config{dp_timinglog}: $!";
+ print $fh "@{[time]} $cpu $lost $avg $max $sdev $store{slots_active}\n"
+ or warn "print >> $config{dp_timinglog}: $!";
+ close $fh
+ or warn "close >> $config{dp_timinglog}: $!";
+ }
return 0 # don't complain if it was less than 0.5%
if $lost < 0.5;
return 0 # don't complain if nobody is looking
}
-# If users for quakenet are listed, parse them into a hash and schedule a sub to query information
-if ($config{irc_quakenet_authusers} ne '') {
- $store{irc_quakenet_users} = { map { $_ => 1 } split / /, $config{irc_quakenet_authusers} };
-
- schedule sub {
- my ($timer) = @_;
- out irc => 0, "PRIVMSG Q :users " . $config{irc_channel};
- schedule $timer => 300;;
- } => 1;
-}
-
# verify that the server is up by letting it echo back a string that causes
# re-initialization of the required aliases
# not containing our own IP:port, or by rcon2irc_eval not being a defined command).
schedule sub {
my ($timer) = @_;
- out dp => 0, 'sv_cmd bans', 'status 1', 'log_dest_udp', 'rcon2irc_eval set dummy 1';
+ out dp => 0, 'sv_cmd banlist', 'status 1', 'log_dest_udp', 'rcon2irc_eval set dummy 1';
$store{status_waiting} = -1;
schedule $timer => (exists $store{dp_hostname} ? $config{dp_status_delay} : 1);;
} => 1;
my ($timer) = @_;
# log on to IRC when needed
- if(exists $store{dp_hostname} && !exists $store{irc_logged_in})
+ if(exists $store{dp_hostname} && !exists $store{irc_seen_welcome})
{
$store{irc_nick_requested} = $config{irc_nick};
out irc => 1, "NICK $config{irc_nick}", "USER $config{irc_user} localhost localhost :$store{dp_hostname}";