Commit Diff


commit - f4d13731461ca8fb7e2f947c3b3ad725ae8ca8fd
commit + 283be9bcdbad1a84d7467f6db1713f09aae8ef84
blob - 22d9e5882533569882794bf4ac15f21313b5211a
blob + c3be6663f1d17a8727366e79dfa83dcee2b10a25
--- mata_bot.pl
+++ mata_bot.pl
@@ -3,18 +3,23 @@ use v5.40;
 
 use Getopt::Std;
 use HTTP::Tiny;
+use IO::Select;
 use IO::Socket qw(AF_INET SOCK_STREAM);
 use IO::Socket::SSL;
 use POSIX qw(strftime);
 
-my $SHAREDIR = '/usr/local/share';
 my $CHUNK_LENGTH = 1024;
+my $CONNECT_TIMEOUT = 60;
+my $RECONNECT_TIME = 60;
+my $SOCK_TIMEOUT = 10;
+my $PING_TIMEOUT = 120;
+my $PONG_TIMEOUT = 60;
 my $DEFAULT_CHAN = '#testmatabot';
 my $DEFAULT_HOST = 'localhost';
 my $DEFAULT_LOGGING = 0;
-my $DEFAULT_PATH_BALL = "$SHAREDIR/matabot/ball";
-my $DEFAULT_PATH_HELLOS = "$SHAREDIR/matabot/hellos";
-my $DEFAULT_PATH_QUOTES = "$SHAREDIR/matabot/quotes";
+my $DEFAULT_PATH_BALL = "/usr/local/share/matabot/ball";
+my $DEFAULT_PATH_HELLOS = "/usr/local/share/matabot/hellos";
+my $DEFAULT_PATH_QUOTES = "/usr/local/share/matabot/quotes";
 my $DEFAULT_PORT = 6667;
 my $DEFAULT_TLS = 0;
 my $MATA_CUTE = '[>.<]';
@@ -219,59 +224,95 @@ sub logger {
 
 sub out {
 	my ($sock, $logging, $message) = @_;
-	logger($message) if $logging;
-	print $sock "$message\r\n";
+	my $s = IO::Select->new($sock);
+	$! = 0;
+	if ($s->can_write($SOCK_TIMEOUT)) {
+		print $sock "$message\r\n";
+		logger('<- ' . $message) if $logging;
+		return 1;
+	} else {
+		if ($!) { logger("!! $!") if $logging; return; }
+		return 0;
+	}
 }
 
 sub evasdrop {
 	my ($sock, $opts, $lists, $subbuffer) = @_;
 	my $buffer = '';
+	my $checkping = 1;
 	my $chunk = '';
 	my $message = '';
+	my $pingtime = time;
+	my $pongtime = time;
+	my $s = IO::Select->new($sock);
 	my %subbuffer;
 	while (1) {
 		my $reply;
-		# buffer up
-		if ($opts->{'tls'}) {
-			$chunk = <$sock>;
-		} else {
-			$sock->recv($chunk, $CHUNK_LENGTH);
-		}
-		$chunk =~ /^([^\r\n]+)(\r\n)?([^\r\n]+)?$/;
-		# keep reading if chunk is empty
-		next if (not $1);
-		# if chunks isn't empty, check for framing point
-		if ($2) {
-			# if we found a framing point, flush buffer and text till framing point
-			$message = $buffer . $1;
-			if ($3) {
-				# if we have text after framing, make it the new content of buffer
-				$buffer = $3;
+		$! = 0;
+		if ($s->can_read($SOCK_TIMEOUT)) {
+			# buffer up
+			if ($opts->{'tls'}) {
+				$chunk = <$sock>;
 			} else {
-				# if we have no text after framing, clear buffer
-				$buffer = '';
+				$sock->recv($chunk, $CHUNK_LENGTH);
 			}
-		} else {
-			# if there's no framing. append chunk to end of buffer and keep reading
-			$buffer .= $chunk;
-			next;
-		}
-		# log message
-		logger($message) if ($opts->{'logging'});
+			if (not $chunk) {
+				logger("!! recv received an empty response");
+				return;
+			}
+			return if not $chunk;
+			$chunk =~ /^([^\r\n]+)(\r\n)?([^\r\n]+)?$/;
+			# keep reading if chunk is empty
+			next if (not $1);
+			# if chunks isn't empty, check for framing point
+			if ($2) {
+				# if we found a framing point, flush buffer and text till framing point
+				$message = $buffer . $1;
+				if ($3) {
+					# if we have text after framing, make it the new content of buffer
+					$buffer = $3;
+				} else {
+					# if we have no text after framing, clear buffer
+					$buffer = '';
+				}
+			} else {
+				# if there's no framing. append chunk to end of buffer and keep reading
+				$buffer .= $chunk;
+				next;
+			}
+			logger('-> ' . $message) if ($opts->{'logging'});
 
-		# respond to message
-		if ($message =~ /^PING :([^\000\r\n\ ]+)$/) {
-			# if we got a ping, pong back
-			$reply = "PONG :$1";
-		} elsif ($message =~ /^:([^\000\r\n\#\&\ ][^\000\r\n\ ]*)![^\000\r\n\ ]+@[^\000\r\n\ ]+ PRIVMSG $opts->{'chan'} :([^\000\r\n]*)$/) {
-			# if we got a message to our chan. read and act accordingly
-			my $sender_nick = $1;
-			my $sender_message = $2;
-			($subbuffer->{$sender_nick}, $reply) = respond($lists, $subbuffer, $sender_nick, $sender_message);
-			next if not $reply;
-			$reply = "PRIVMSG $opts->{'chan'} :" . $reply;
+			# respond to message
+			if ($message =~ /^PING :([^\000\r\n\ ]+)$/) {
+				# if we got a ping, pong back
+				$reply = "PONG :$1";
+			} elsif ($message =~ /^:[^\000\r\n ]+ PONG/) {
+				# if server ponged us back, reset ping-pong and allow pinging again
+				$pingtime = time;
+				$pongtime = time;
+				$checkping = 1;
+			} elsif ($message =~ /^:([^\000\r\n\#\&\ ][^\000\r\n\ ]*)![^\000\r\n\ ]+@[^\000\r\n\ ]+ PRIVMSG $opts->{'chan'} :([^\000\r\n]*)$/) {
+				# if we got a message to our chan. read and act accordingly
+				my $sender_nick = $1;
+				my $sender_message = $2;
+				($subbuffer->{$sender_nick}, $reply) = respond($lists, $subbuffer, $sender_nick, $sender_message);
+				$reply = "PRIVMSG $opts->{'chan'} :" . $reply if $reply;
+			}
+			out($sock, $opts->{'logging'}, $reply) if $reply;
+		} else {
+			if ($!) { logger("!! ${!}") if $opts->{'logging'}; return; }
 		}
-		out($sock, $opts->{'logging'}, $reply);
+			
+		# ping-pong
+		if ($checkping and time-$pingtime >= $PING_TIMEOUT) {
+			# ping server every once in a while and wait for pong
+			out($sock, $opts->{'logging'}, "PING $opts->{'host'}");
+			$checkping = 0;
+		} elsif (not $checkping and time-$pongtime >= $PONG_TIMEOUT) {
+			# we leaving if we don't get ponged on time
+			logger("!! PONG response from server timed out") if $opts->{'logging'};
+			return
+		}
 	}
 }
 
@@ -318,30 +359,49 @@ sub init {
 	};
 }
 
+my ($opts, $lists) = init();
 while (1) {
 	# start the connection
-	my ($opts, $lists) = init();
 	my $sock;
 	if ($opts->{'tls'}) {
-		$sock = IO::Socket::SSL->new(
+		if (not $sock = IO::Socket::SSL->new(
 			Domain => AF_INET,
+			Timeout => $CONNECT_TIMEOUT,
 			Type => SOCK_STREAM,
 			PeerHost => $opts->{'host'},
 			PeerPort => $opts->{'port'},
-		) || die "Can't open socket: $IO::Socket::errstr";
+		)) {
+			logger("!! can't open socket: $IO::Socket::errstr") if $opts->{'logging'};
+			$sock = undef;
+		}
 	} else {
-		$sock = IO::Socket->new(
+		if (not $sock = IO::Socket->new(
 			Domain => AF_INET,
+			Timeout => $CONNECT_TIMEOUT,
 			Type => SOCK_STREAM,
 			proto => $opts->{'tcp'},
 			PeerHost => $opts->{'host'},
 			PeerPort => $opts->{'port'},
-		) || die "Can't open socket: $IO::Socket::errstr";
+		)) {
+			logger("!! can't open socket: $IO::Socket::errstr") if $opts->{'logging'};
+			$sock = undef;
+		}
 	}
-	# set user, real, and nick, then join
-	out($sock, $opts->{'logging'}, "USER $USER * * :$REAL");
-	out($sock, $opts->{'logging'}, "NICK $NICK");
-	out($sock, $opts->{'logging'}, "JOIN $opts->{'chan'}");
-	# main loop
-	evasdrop($sock, $opts, $lists);
+	if ($sock) {
+		# set user, real, and nick, then join
+		out($sock, $opts->{'logging'}, "USER $USER * * :$REAL");
+		out($sock, $opts->{'logging'}, "NICK $NICK");
+		out($sock, $opts->{'logging'}, "JOIN $opts->{'chan'}");
+		# main loop
+		evasdrop($sock, $opts, $lists);
+		# end session and sleep a bit before reconnecting
+		out($sock, $opts->{'logging'}, "QUIT");
+		if ($opts->{'tls'}) {
+			close($sock);
+		} else {
+			$sock->close();
+		}
+	}
+	logger("!! reconnecting in ${RECONNECT_TIME} seconds...") if $opts->{'logging'};
+	sleep $RECONNECT_TIME;
 }
blob - 511825896c9e8f14f71bb16b3ae607724734f14c
blob + ffa522e38d93f4cbcf6f69707dcaacad7d4b2855
--- matabot.8
+++ matabot.8
@@ -174,10 +174,5 @@ Noodle (noodle) is the only person in chat who can giv
 (It's hard-coded.
 Sorry).
 .Sh BUGS
-The bot doesn't have the ability te re-connect on network loss yet.
-You can
-.Sq rcctl restart matabot
-for now.
-.Pp
 The bot cannot detach from terminal yet to become a daemon.
 daemon-izing is handled in the rc service script.