Commit Diff


commit - 3af4a1f76a207c8113cce7539b513a3af26dff8d
commit + 16d0d03f980f192b1d5fbb3efbce9ed1aaf2f1ed
blob - 9579989b001f202a240eeb511659dd4d28608520
blob + 7ae4d09317a961dd6028db9384ccd6f4abd07b84
--- mata_bot.pl
+++ mata_bot.pl
@@ -13,6 +13,7 @@ use constant {
 	CONNECT_TIMEOUT	=> 60,
 	CRLF		=> 2,		# RFC 2812
 	DEFAULT_PORT	=> 6667,
+	DEFAULT_RSS	=> 0,
 	DEFAULT_TLS	=> 0,
 	HOSTMAX		=> 63,		# RFC 2812
 	IRCMAX		=> 512,
@@ -21,6 +22,7 @@ use constant {
 	NBIBLE		=> 31102,
 	NQURAN		=> 6348,
 	RECONN_SLEEP	=> 60,
+	RSS_CHECK_TIME	=> 3600,
 	SOCK_TIMEOUT	=> 10,
 };
 my $DEFAULT_CHAN	= '#testmatabot';
@@ -39,6 +41,8 @@ my $MYNICK 		= 'mata_bot';
 my $MYREAL 		= 'death to technomage!!';
 my $MYUSER 		= 'mata_bot_beta4';
 my $NICKRE 		= qr/mata_?bo[ity]+/i;
+my $RSSLINK		= 'https://analognowhere.com/feed/rss.xml';
+my $RSSMEM_PATH		= 'rss';
 
 sub randint {
 	my($min, $max) = @_;
@@ -294,15 +298,64 @@ sub recvmsg {
 	}
 }
 
+sub sendnews {
+	state $lastlink;
+	my $rssmem = shift;
+	my ($homepage, $r);
+	my (@matches, @replies);
+
+	$r = HTTP::Tiny->new->get($RSSLINK);
+	unless ($r->{success}) {
+		logger(LOG_WARN, 'GET ${RSSLINK}: $r->{status} $r->{reason}');
+		return ();
+	}
+	unless (length $r->{content}) {
+		logger(LOG_WARN, 'GET ${RSSLINK}: empty HTTP response');
+		return ();
+	}
+	unless (defined($lastlink)) {
+		seek($rssmem, 0, 0);
+		chomp($lastlink = <$rssmem>);
+	}
+	$homepage = ($r->{content} =~ m,<channel>.*?<link>([^<]+)</link>,s) ? $1 : 'website';
+	while ($r->{content} =~ m,\G.*?<item>.*?<title>([^<]+)</title>.*?<link>([^<]+)</link>.*?</item>,gs) {
+		push @matches, {title => $1, link => $2};
+	}
+	if (@matches) {
+		my $i;
+
+		if (defined($lastlink)) {
+			for ($i = 0; $i < @matches; $i++) {
+				last if $matches[$i]->{'link'} eq $lastlink;
+			}
+		} else {
+			$i = @matches;
+		}
+		for ($i--; $i >= 0; $i--) {
+			push @replies,  "RSS: $matches[$i]->{'link'} | $matches[$i]->{'title'}";
+			if ($i > 2) {
+				push @replies, "RSS: found " . ($i-1) . " new items in between.  Visit ${homepage} for more...";
+				$i = 1;
+			}
+		}
+		truncate($rssmem, 0);
+		seek($rssmem, 0, 0);
+		say $rssmem ($lastlink = $matches[0]->{'link'});
+		$rssmem->flush;
+	}
+	return @replies;
+}
+
 sub evasdrop {
-	my ($s, $host, $chan, $lists) = @_;
-	my ($msgtime, $msgmax, $pingsent, $pingtime, $prefixlen, $priv);
+	my ($s, $rssmem, $lists, ($chan, $host, $rss)) = @_;
+	my ($firstrss, $msgmax, $msgtime, $pingsent, $pingtime, $priv, $rsstime);
 
+	$firstrss = 1;
 	$pingsent = 0;
 	$priv = "PRIVMSG ${chan} :";
 	$msgmax = IRCMAX - length(":${MYNICK}!~${MYUSER}\@ ${priv}")
 		- HOSTMAX - CRLF; # conservative max message length heuristic
-	$msgtime = $pingtime = time;
+	$rsstime = $msgtime = $pingtime = time;
 	while (1) {
 		if ($pingsent) {
 			if (time - $pingtime > MAX_LAG) {
@@ -316,6 +369,13 @@ sub evasdrop {
 			$pingsent = 1;
 			$pingtime = time;
 		}
+		if ($rss and ($firstrss || time - $rsstime > RSS_CHECK_TIME)) {
+			foreach (sendnews($rssmem)) {
+				sendmsg($s, $priv . substr($_,0,$msgmax));
+			}
+			$rsstime = time;
+			$firstrss = 0 if $firstrss;
+		}
 		defined($_ = recvmsg($s)) or return;
 		next if not length;
 		$msgtime = time;
@@ -343,18 +403,20 @@ sub evasdrop {
 }
 
 sub usage {
-	say STDERR "usage: ${0} [-d|-v] [-t] [-b path] [-e path] [-h host] [-j join] [-p port] [-q path]";
+	say STDERR "usage: ${0} [-d|-v] [-r] [-t] [-b path] [-e path] [-h host] [-j join] [-p port] [-q path]";
 	exit 1;
 }
 
 sub init {
 	my ($path_ball, $path_helo, $path_quot);
+	my $rssmem;
 	my %opts;
 
 	@_ = @ARGV;
 	while (@_) {
 		$_ = shift;
 		if (/^-d$/) { logger(LOG_DEBUG) }
+		elsif (/^-r$/) { $opts{'rss'} = 1 }
 		elsif (/^-t$/) { $opts{'tls'} = 1 }
 		elsif (/^-v$/) { logger(LOG_WARN) }
 		elsif (@_ < 1) { usage() }
@@ -369,6 +431,7 @@ sub init {
 	$opts{'chan'} //= $DEFAULT_CHAN;
 	$opts{'host'} //= $DEFAULT_HOST;
 	$opts{'port'} //= DEFAULT_PORT;
+	$opts{'rss'} //= DEFAULT_RSS;
 	$opts{'tls'} //= DEFAULT_TLS;
 	open(my $ball_file, '<', $path_ball // $DEFAULT_PATH_BALL)
 		or die "couldn't open ${path_ball}: $!";
@@ -382,14 +445,18 @@ sub init {
 		or die "couldn't open ${path_quot}: $!";
 	chomp(my @quot = <$quot_file>);
 	close $quot_file or die "${quot_file}: $!";
-	return \%opts, [
+	if ($opts{'rss'}) {
+		open($rssmem, '+>>', $RSSMEM_PATH)
+			|| die "couldn't open ${RSSMEM_PATH}: $!";
+	}
+	return $rssmem, \%opts, [
 		\@ball,
 		\@helo,
 		\@quot,
 	];
 }
 
-my ($opts, $lists) = init();
+my ($rssmem, $opts, $lists) = init();
 
 while (1) {
 	my ($sock, $addr);
@@ -410,7 +477,7 @@ while (1) {
 		sendmsg($s, "NICK ${MYNICK}");
 		sendmsg($s, "WHO ${MYNICK}");
 		sendmsg($s, "JOIN $opts->{'chan'}");
-		evasdrop($s, $opts->{'host'}, $opts->{'chan'}, $lists);
+		evasdrop($s, $rssmem, $lists, @$opts{'chan', 'host', 'rss'});
 		sendmsg($s, 'QUIT');
 		$sock->close();
 	} else {