X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fxonotic.git;a=blobdiff_plain;f=server%2Frcon2irc%2Frcon2irc.pl;h=cb800e84a2876e3c0e19678343c448e5eaa4f7b5;hp=07de35223a808832e0d9395ebf6bb66ef15f4e89;hb=207cf23b36ad8a8583ffb3670dbb2ffd46f5c7b5;hpb=e5832bf2ecac60a672a29c85cd8dec341a215e1c diff --git a/server/rcon2irc/rcon2irc.pl b/server/rcon2irc/rcon2irc.pl index 07de3522..cb800e84 100755 --- a/server/rcon2irc/rcon2irc.pl +++ b/server/rcon2irc/rcon2irc.pl @@ -28,6 +28,7 @@ our $VERSION = '0.4.2 svn $Revision$'; # 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 @@ -57,7 +58,7 @@ sub color_irc2dp($) } 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', '.', '<', '=', '>', @@ -90,16 +91,28 @@ our @text_qfont_table = ( # ripped from DP console.c qfont_table '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) : @@ -119,7 +132,7 @@ sub color_dp2none($) { my ($type, $data, $next) = @_; $type eq 'char' - ? $text_qfont_table[ord $data] + ? text_qfont_table $data : ""; } $message; @@ -196,7 +209,7 @@ sub color_dp2irc($) $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]; @@ -225,7 +238,7 @@ sub color_dp2ansi($) $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]; @@ -253,6 +266,8 @@ sub color_dpfix($) # 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. @@ -306,6 +321,7 @@ sub new($$) 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 @@ -327,6 +343,14 @@ sub sockname($) 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($$) { @@ -465,6 +489,8 @@ sub join_commands($@) sub send($$$) { my ($self, $line, $nothrottle) = @_; + utf8::encode $line + if $color_utf8_enable; if($self->{secure} > 1) { $self->{connector}->send("\377\377\377\377getchallenge"); @@ -514,7 +540,7 @@ sub recvchallenge($) 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; } @@ -544,7 +570,10 @@ sub recv($) 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; } @@ -611,6 +640,8 @@ sub throttle($$$) sub send($$$) { my ($self, $line, $nothrottle) = @_; + utf8::encode $line + if $color_utf8_enable; my $t = time(); if(defined $self->{capacity}) { @@ -652,7 +683,10 @@ sub recv($) 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; } @@ -716,6 +750,8 @@ our %config = ( dp_password => undef, dp_status_delay => 30, dp_server_from_wan => "", + dp_listen_from_server => "", + dp_utf8_enable => $color_utf8_enable, irc_local => "", irc_admin_password => "", @@ -723,15 +759,29 @@ our %config = ( 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"; +} + -# Nexuiz specific parsing of some server messages +# Xonotic specific parsing of some server messages -sub nex_slotsstring() +sub xon_slotsstring() { my $slotsstr = ""; if(defined $store{slots_max}) @@ -739,8 +789,8 @@ sub nex_slotsstring() 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}; - $slotsstr .= "; join now: \002nexuiz +connect $s" + 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}; } return $slotsstr; @@ -769,6 +819,7 @@ my @missing = grep { !defined $config{$_} } keys %config; 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... @@ -795,6 +846,7 @@ $SIG{TERM} = sub { $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); @@ -862,7 +914,7 @@ sub irc_error() 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 { @@ -1011,10 +1063,17 @@ sub irc_joinstage($) # 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; @@ -1049,8 +1108,8 @@ sub cond($) [ 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; } ], @@ -1104,7 +1163,7 @@ sub cond($) } else { - my $slotsstr = nex_slotsstring(); + my $slotsstr = xon_slotsstring(); out irc => 0, "PRIVMSG $config{irc_channel} :\001ACTION can be joined again$slotsstr!\001"; } } @@ -1293,6 +1352,18 @@ sub cond($) [ 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); } ], @@ -1309,12 +1380,23 @@ sub cond($) 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; @@ -1358,7 +1440,7 @@ sub cond($) # 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; @@ -1383,22 +1465,14 @@ sub cond($) 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: Nexuiz server -> IRC channel + # chat: Xonotic server -> IRC channel [ dp => q{\001(.*?)\^7: (.*)} => sub { my ($nick, $message) = map { color_dp2irc $_ } @_; out irc => 0, "PRIVMSG $config{irc_channel} :<$nick\017> $message"; return 0; } ], - # chat: Nexuiz server -> IRC channel, nick set + # chat: Xonotic server -> IRC channel, nick set [ dp => q{:join:(\d+):(\d+):([^:]*):(.*)} => sub { my ($id, $slot, $ip, $nick) = @_; $store{"playernickraw_byid_$id"} = $nick; @@ -1410,7 +1484,7 @@ sub cond($) return 0; } ], - # chat: Nexuiz server -> IRC channel, nick change/set + # chat: Xonotic server -> IRC channel, nick change/set [ dp => q{:name:(\d+):(.*)} => sub { my ($id, $nick) = @_; $store{"playernickraw_byid_$id"} = $nick; @@ -1421,7 +1495,7 @@ sub cond($) return 0; } ], - # chat: Nexuiz server -> IRC channel, vote call + # chat: Xonotic server -> IRC channel, vote call [ dp => q{:vote:vcall:(\d+):(.*)} => sub { my ($id, $command) = @_; $command = color_dp2irc $command; @@ -1430,7 +1504,7 @@ sub cond($) return 0; } ], - # chat: Nexuiz server -> IRC channel, vote stop + # chat: Xonotic server -> IRC channel, vote stop [ dp => q{:vote:vstop:(\d+)} => sub { my ($id) = @_; my $oldnick = $id ? $store{"playernick_byid_$id"} : "(console)"; @@ -1438,7 +1512,7 @@ sub cond($) return 0; } ], - # chat: Nexuiz server -> IRC channel, master login + # chat: Xonotic server -> IRC channel, master login [ dp => q{:vote:vlogin:(\d+)} => sub { my ($id) = @_; my $oldnick = $id ? $store{"playernick_byid_$id"} : "(console)"; @@ -1446,7 +1520,7 @@ sub cond($) return 0; } ], - # chat: Nexuiz server -> IRC channel, master do + # chat: Xonotic server -> IRC channel, master do [ dp => q{:vote:vdo:(\d+):(.*)} => sub { my ($id, $command) = @_; $command = color_dp2irc $command; @@ -1455,7 +1529,7 @@ sub cond($) return 0; } ], - # chat: Nexuiz server -> IRC channel, result + # chat: Xonotic server -> IRC channel, result [ dp => q{:vote:v(yes|no|timeout):(\d+):(\d+):(\d+):(\d+):(-?\d+)} => sub { my ($result, $yes, $no, $abstain, $not, $min) = @_; my $spam = "$yes:$no" . (($min >= 0) ? " ($min needed)" : "") . ", $abstain didn't care, $not didn't vote"; @@ -1463,7 +1537,7 @@ sub cond($) return 0; } ], - # chat: IRC channel -> Nexuiz server + # chat: IRC channel -> Xonotic server [ irc => q{:([^! ]*)![^ ]* (?i:PRIVMSG) (?i:(??{$config{irc_channel}})) :(?i:(??{$store{irc_nick}}))(?: |: ?|, ?)(.*)} => sub { my ($nick, $message) = @_; $nick = color_dpfix $nick; @@ -1505,7 +1579,7 @@ sub cond($) $store{map} = $map; $store{map_starttime} = time(); if ($config{irc_announce_mapchange} eq 'always' || ($config{irc_announce_mapchange} eq 'notempty' && $store{slots_active} > 0)) { - my $slotsstr = nex_slotsstring(); + my $slotsstr = xon_slotsstring(); out irc => 0, "PRIVMSG $config{irc_channel} :\00304" . $map . "\017 has begun$slotsstr"; } delete $store{lms_blocked}; @@ -1518,7 +1592,7 @@ sub cond($) return 0; } ], - # scores: Nexuiz server -> IRC channel (start) + # scores: Xonotic server -> IRC channel (start) [ dp => q{:scores:(.*):(\d+)} => sub { my ($map, $time) = @_; $store{scores} = {}; @@ -1529,7 +1603,7 @@ sub cond($) return 0; } ], - # scores: Nexuiz server -> IRC channel, legacy format + # scores: Xonotic server -> IRC channel, legacy format [ dp => q{:player:(-?\d+):(\d+):(\d+):(\d+):(\d+):(.*)} => sub { my ($frags, $deaths, $time, $team, $id, $name) = @_; return if not exists $store{scores}; @@ -1538,7 +1612,7 @@ sub cond($) return 0; } ], - # scores: Nexuiz server -> IRC channel (CTF), legacy format + # scores: Xonotic server -> IRC channel (CTF), legacy format [ dp => q{:teamscores:(\d+:-?\d*(?::\d+:-?\d*)*)} => sub { my ($teams) = @_; return if not exists $store{scores}; @@ -1546,7 +1620,7 @@ sub cond($) return 0; } ], - # scores: Nexuiz server -> IRC channel, new format + # scores: Xonotic server -> IRC channel, new format [ dp => q{:player:see-labels:(-?\d+)[-0-9,]*:(\d+):(\d+):(\d+):(.*)} => sub { my ($frags, $time, $team, $id, $name) = @_; return if not exists $store{scores}; @@ -1554,7 +1628,7 @@ sub cond($) return 0; } ], - # scores: Nexuiz server -> IRC channel (CTF), new format + # scores: Xonotic server -> IRC channel (CTF), new format [ dp => q{:teamscores:see-labels:(-?\d+)[-0-9,]*:(\d+)} => sub { my ($frags, $team) = @_; return if not exists $store{scores}; @@ -1562,7 +1636,7 @@ sub cond($) return 0; } ], - # scores: Nexuiz server -> IRC channel + # scores: Xonotic server -> IRC channel [ dp => q{:end} => sub { return if not exists $store{scores}; my $s = $store{scores}; @@ -1688,17 +1762,6 @@ for my $p(split ' ', $config{plugins}) } -# 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 @@ -1726,7 +1789,7 @@ schedule sub { 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}";