Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
306 changes: 176 additions & 130 deletions nagibot.pl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
nagios_msg_fifo => '/usr/local/nagios/var/rw/nagibot.fifo',
nagios_status_log_file => '/usr/local/nagios/var/status.dat',
htmlify_msg => 0,
max_retry_interval => 600,
);

%bot_command = (
Expand Down Expand Up @@ -164,164 +165,189 @@
# Add namespace for showing idle time
AnyEvent::XMPP::Namespaces::set_xmpp_ns_alias('last', 'jabber:iq:last');

CON:
$event = AnyEvent->condvar;
my $endtime = time() + $config->{retry_timeout} if $config->{retry_timeout};
my $tts = 2;

$connection = AnyEvent::XMPP::IM::Connection->new (
jid => $jid,
password => $config->{'password'},
resource => $config->{'bot_resource'},
initial_presence => 0,
);

$connection->reg_cb (
# On Connect
session_ready => sub {
my ($con) = @_;
print 'Connected as ' . $con->jid . "\n" if $verbose;

print "Setting presence ...\n" if $verbose > 1;
$con->send_presence (
undef,
undef,
show => $config->{'bot_status'},
status => $config->{'bot_status_text'},
priority => $config->{'bot_status_priority'}
);

print "Joining rooms (if any) ...\n" if $verbose > 1;
$con->add_extension ($disco = AnyEvent::XMPP::Ext::Disco->new);
$con->add_extension (
$muc = AnyEvent::XMPP::Ext::MUC->new (disco => $disco, connection => $con)
);
for my $room (keys %$rooms) {
$muc->join_room (
$con,
$room,
$rooms->{$room},
timeout=>20,
create_instant => 0,
history => {stanzas => 0}
);
}
until ($terminating) {
$event = AnyEvent->condvar;

print "Register room message handler ...\n" if $verbose > 1;
$muc->reg_cb (
message => sub {
my ($con, $room, $msg, $is_echo) = @_;

return if $is_echo;
$connection = AnyEvent::XMPP::IM::Connection->new (
jid => $jid,
password => $config->{'password'},
resource => $config->{'bot_resource'},
initial_presence => 0,
);

print "Message (" . $room->jid .") from " . $msg->from . ": " . $msg->any_body . "\n" if $verbose > 2;
$connection->reg_cb (
# On Connect
session_ready => sub {
my ($con) = @_;
print 'Connected as ' . $con->jid . "\n" if $verbose;

print "Setting presence ...\n" if $verbose > 1;
$con->send_presence (
undef,
undef,
show => $config->{'bot_status'},
status => $config->{'bot_status_text'},
priority => $config->{'bot_status_priority'}
);

my $my_name = 'nagibot';
if (my $me = $room->get_me) {
$my_name = lc $me->nick;
print "Joining rooms (if any) ...\n" if $verbose > 1;
$con->add_extension ($disco = AnyEvent::XMPP::Ext::Disco->new);
$con->add_extension (
$muc = AnyEvent::XMPP::Ext::MUC->new (
disco => $disco,
connection => $con
)
);

for my $room (keys %$rooms) {
$muc->join_room (
$con,
$room,
$rooms->{$room},
timeout => 20,
create_instant => 0,
history => {stanzas => 0}
);
}

print "Register room message handler ...\n" if $verbose > 1;
$muc->reg_cb (
message => sub {
my ($con, $room, $msg, $is_echo) = @_;

return if $is_echo;

print "Message (" . $room->jid .") from " . $msg->from . ": " . $msg->any_body . "\n" if $verbose > 2;

my $my_name = 'nagibot';
if (my $me = $room->get_me) {
$my_name = lc $me->nick;
}
my $cmd = $1 if $msg->any_body =~ /^(?:\@$my_name|$my_name:)\s+(.*)$/i;
return unless $cmd;
&SetIdlePresence(0);
&SendRoomMessage($room, &ProcessBotCommand ($msg->from, $cmd));
&StartTimer($config->{'bot_show_idle_after'});
}
my $cmd = $1 if $msg->any_body =~ /^(?:\@$my_name|$my_name:)\s+(.*)$/i;
return unless $cmd;
&SetIdlePresence(0);
&SendRoomMessage($room, &ProcessBotCommand ($msg->from, $cmd));
&StartTimer($config->{'bot_show_idle_after'});
}

);
);

&StartTimer($config->{'bot_show_idle_after'});
},
&StartTimer($config->{'bot_show_idle_after'});

# Reset reconnection parameters
$endtime = time() + $config->{retry_timeout} if $config->{retry_timeout};
$tts = 2;
},

# On Receive
message => sub {
my ($con, $msg) = @_;
# On Receive
message => sub {
my ($con, $msg) = @_;

return unless $msg->any_body;
return unless $msg->any_body;

my $from = $con->get_roster()->get_contact($msg->from);
my $from = $con->get_roster()->get_contact($msg->from);

print "Message from " , $msg->from , "(", $from->is_on_roster() ? "" : "NOT " , "on roster): " , $msg->any_body , "\n" if $verbose > 2;
return unless $from->is_on_roster;
print "Message from " , $msg->from , "(", $from->is_on_roster() ? "" : "NOT " , "on roster): " , $msg->any_body , "\n" if $verbose > 2;
return unless $from->is_on_roster;

&SetIdlePresence(0);
my $reply = $msg->make_reply;
$reply->add_body (&ProcessBotCommand($msg->from, $msg->any_body) => undef);
$reply->send($con);
&StartTimer($config->{'bot_show_idle_after'});
},
&SetIdlePresence(0);
my $reply = $msg->make_reply;
$reply->add_body (&ProcessBotCommand($msg->from, $msg->any_body) => undef);
$reply->send($con);
&StartTimer($config->{'bot_show_idle_after'});
},

# On Error
error => sub {
my ($con, $error) = @_;
# On Error
error => sub {
my ($con, $error) = @_;

warn "Error: " . $error->string . "\n";
},
# On connect
connect => sub {
my ($con, $h, $p) = @_;
print "connected to $h:$p\n" if $verbose > 1;
},
warn "Error: " . $error->string . "\n";
},
# On connect
connect => sub {
my ($con, $h, $p) = @_;
print "connected to $h:$p\n" if $verbose > 1;
},

# On Disconnect
disconnect => sub {
my ($con, $h, $p, $reason) = @_;
print "Disconnected from $h:$p: $reason\n";
$event->send;
},
# On Disconnect
disconnect => sub {
my ($con, $h, $p, $reason) = @_;
if (defined($h) && defined($p)) {
$reason = "Disconnected from $h:$p: $reason";
}
print "$reason\n";
$event->send;
},

);
);

print "Trying to connect ...\n" if $verbose;
$connection->connect ();
print "Trying to connect ...\n" if $verbose;
$connection->connect ();

print "Opening fifo for reading ...\n" if $verbose;
open (FIFO, "+< " . $config->{'nagios_msg_fifo'}) or die "Cannot open fifo " . $config->{'nagios_msg_fifo'} .": $!";
binmode FIFO, ':encoding(' . $config->{'incoming_charset'} . ')';
print "Opening fifo for reading ...\n" if $verbose;
open (FIFO, "+< " . $config->{'nagios_msg_fifo'}) or die "Cannot open fifo " . $config->{'nagios_msg_fifo'} .": $!";
binmode FIFO, ':encoding(' . $config->{'incoming_charset'} . ')';

$io_event = AnyEvent->io (
fh => \*FIFO,
poll => 'r',
cb => sub {
return unless $connection->is_connected();

$io_event = AnyEvent->io (
fh => \*FIFO,
poll => 'r',
cb => sub {
return unless $connection->is_connected();
chomp (my $input = <FIFO>);
print "read: $input\n" if $verbose > 2;

chomp (my $input = <FIFO>);
print "read: $input\n" if $verbose > 2;
# Reset idle presence
&SetIdlePresence(0);

# Reset idle presence
&SetIdlePresence(0);
# Send message to all rooms
foreach my $room (keys %$rooms) {
my $r = $muc->get_room($connection, $room);
unless ($r) {
print "ERROR: Lost connection to the room $room";
# TODO: Implement rejoin
return;
}
my $msg = $r->make_message (body => $input);
&HtmlifyMsg($msg, $input) if $config->{'htmlify_msg'};
$msg->send ();
}

# Send message to all rooms
foreach my $room (keys %$rooms) {
my $r = $muc->get_room($connection, $room);
unless ($r) {
print "ERROR: Lost connection to the room $room";
# TODO: Implement rejoin
return;
# send message to all users
foreach my $ujid (@$jids) {
my $msg = AnyEvent::XMPP::IM::Message->new (to => $ujid, body => $input, type => 'chat');
&HtmlifyMsg($msg, $input) if $config->{'htmlify_msg'};
$msg->send ($connection);
}
my $msg = $r->make_message (body => $input);
&HtmlifyMsg($msg, $input) if $config->{'htmlify_msg'};
$msg->send ();
}

# send message to all users
foreach my $ujid (@$jids) {
my $msg = AnyEvent::XMPP::IM::Message->new (to => $ujid, body => $input, type => 'chat');
&HtmlifyMsg($msg, $input) if $config->{'htmlify_msg'};
$msg->send ($connection);
# Restart idle timer
&StartTimer($config->{'bot_show_idle_after'});
}
);

# Restart idle timer
&StartTimer($config->{'bot_show_idle_after'});
}
);

$event->wait;
$event->wait;

undef $io_event;
undef $timer_event;
undef $connection;
undef $io_event;
undef $timer_event;
undef $connection;

# reconnect ?
goto CON unless ($terminating);
last if $terminating;
if (defined($endtime) && time() > $endtime) {
print "Disconnected. Giving up.\n";
}

my $t = int(rand(0xffffffff)) % ($tts - 1) + 1;
print "Disconnected. Sleeping for $t seconds before retry...\n" if $verbose;
sleep($t);
if ($tts < $config->{max_retry_interval}) {
$tts <<= 1;
if ($tts < 0 || $tts > $config->{max_retry_interval}) {
$tts = $config->{max_retry_interval};
}
}
}

print "Quitting.\n" if $verbose;

Expand Down Expand Up @@ -882,6 +908,26 @@ =head1 CONFIGURATION FILE
Name of the pid file if the bot forks into background. (defaults to
/var/run/nagibot/<local-part-of-jid>.pid)

=item B<max_retry_interval>

When B<nagibot> gets disconnected from the XMPP server, it tries to
reconnect. It can happen that the attempt to reconnect will fail too
(e.g. if the remote server is down or heavy loaded). In order to limit
the use of system resources and minimize the eventual impact on the XMPP
server, the interval between two subsequent failed reconnection attempts
is selected as a random number between 1 and certain upper limit, which
grows exponentially. The initial value of that upper limit is 2 seconds.
It gets doubled after each subsequent failure. The B<max_retry_interval>
parameter sets the maximum value for that limit, after which it is reset
to 2 seconds. The default value is 600 seconds.

=item B<retry_timeout>

By default, B<nagibot> will retry connections to the XMPP server forever
(see the description under B<max_retry_interval>). Setting B<retry_interval>
instructs it to give up after it is unable to connect to the server for
that many seconds.

=back

=head1 SAMPLE CONFIGURATION
Expand Down