commit bed1c8b3df1d2b12cf73a38fc42f12bfc165211b
parent 05d86fdaa37573e395f40904af86c8516c40456d
Author: noodle <noodle@pastanoggin.com>
Date: Sat, 21 Jun 2025 21:18:34 +0300
purify functional mutation of globals/parameters
Diffstat:
M | mata_bot.pl | | | 241 | ++++++++++++++++++++++++++++++++++++++++++------------------------------------- |
1 file changed, 127 insertions(+), 114 deletions(-)
diff --git a/mata_bot.pl b/mata_bot.pl
@@ -9,56 +9,69 @@ use POSIX qw(strftime);
my $SHAREDIR = '/usr/local/share';
my $CHUNK_LENGTH = 1024;
+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_PORT = 6667;
+my $DEFAULT_TLS = 0;
+my $MATA_CUTE = '[>.<]';
+my $MATA_DEAD = '[x~x]';
+my $MATA_FLIPOFF = 't[-_-t]';
+my $MATA_HAPPY = '[^_^]';
+my $MATA_NORM = '[._.]';
+my $MATA_SING = '[^=^]';
+my $MOTHER = 'noodle';
my $NICK = 'mata_bot';
my $NICK_RE = qr/mata_?bo[ity]+/i;
-my $USER = 'mata_bot_beta4';
my $REAL = 'death to technomage!!';
-my $MOTHER = 'noodle';
-my $MATA_NORM = '[._.]';
-my $MATA_HAPPY = '[^_^]';
-my $MATA_SING = '[^=^]';
-my $MATA_DEAD = '[x~x]';
-my $MATA_CUTE = '[>.<]';
-my $MATA_FLIPOFF = 't[-_-t]';
-
-my $ball_path = "$SHAREDIR/matabot/ball";
-my $quotes_path = "$SHAREDIR/matabot/quotes";
-my $hellos_path = "$SHAREDIR/matabot/hellos";
-my $chan = '#testmatabot';
-my $host = 'localhost';
-my $logging = 0;
-my $port = '6667';
-my $tls = 0;
-my %subbuffer;
+my $USER = 'mata_bot_beta4';
-# process args
-getopts('tlH:b:h:j:p:q:', \my %opts);
-$ball_path = $opts{'b'} if ($opts{'b'});
-$chan = "#$opts{'j'}" if ($opts{'j'});
-$hellos_path = $opts{'H'} if ($opts{'H'});
-$host = $opts{'h'} if ($opts{'h'});
-$logging = 1 if ($opts{'l'});
-$port = $opts{'p'} if ($opts{'p'});
-$quotes_path = $opts{'q'} if ($opts{'q'});
-$tls = 1 if ($opts{'t'});
+sub init {
+ my $ball_path = $DEFAULT_PATH_BALL;
+ my $chan = $DEFAULT_CHAN;
+ my $hellos_path = $DEFAULT_PATH_HELLOS;
+ my $host = $DEFAULT_HOST;
+ my $logging = $DEFAULT_LOGGING;
+ my $port = $DEFAULT_PORT;
+ my $quotes_path = $DEFAULT_PATH_QUOTES;
+ my $tls = $DEFAULT_TLS;
-# read in the quotes file;
-open (my $quotes_file, "<", $quotes_path) or die "couldn't open ${quotes_path}: $!";
-chomp(my @quotes = <$quotes_file>);
-my $quotes_num = $.;
-close $quotes_file or die "$quotes_file: $!";
+ getopts('tlH:b:h:j:p:q:', \my %flags);
+ $ball_path = $flags{'b'} if $flags{'b'};
+ $chan = "#$flags{'j'}" if $flags{'j'};
+ $hellos_path = $flags{'H'} if $flags{'H'};
+ $host = $flags{'h'} if $flags{'h'};
+ $logging = 1 if $flags{'l'};
+ $port = $flags{'p'} if $flags{'p'};
+ $quotes_path = $flags{'q'} if $flags{'q'};
+ $tls = 1 if $flags{'t'};
-# read in the 8ball file
-open (my $ball_file, "<", $ball_path) or die "couldn't open ${ball_path}: $!";
-chomp(my @ball = <$ball_file>);
-my $ball_num = $.;
-close $ball_file or die "$ball_file: $!";
+ open (my $ball_file, "<", $ball_path) or die "couldn't open ${ball_path}: $!";
+ chomp(my @ball = <$ball_file>);
+ close $ball_file or die "$ball_file: $!";
+ open (my $hellos_file, "<", $hellos_path) or die "couldn't open ${hellos_path}: $!";
+ chomp(my @hellos = <$hellos_file>);
+ close $hellos_file or die "$hellos_file: $!";
+ open (my $quotes_file, "<", $quotes_path) or die "couldn't open ${quotes_path}: $!";
+ chomp(my @quotes = <$quotes_file>);
+ close $quotes_file or die "$quotes_file: $!";
-# read in the hellos file
-open (my $hellos_file, "<", $hellos_path) or die "couldn't open ${hellos_path}: $!";
-chomp(my @hellos = <$hellos_file>);
-my $hellos_num = $.;
-close $hellos_file or die "$hellos_file: $!";
+ return {
+ 'chan' => $chan,
+ 'host' => $host,
+ 'logging' => $logging,
+ 'port' => $port,
+ 'tls' => $tls,
+ },
+ {
+ 'ball' => \@ball,
+ 'hellos' => \@hellos,
+ 'quotes' => \@quotes,
+ };
+}
sub randint {
my($min, $max) = @_;
@@ -73,18 +86,18 @@ sub logger {
}
sub out {
- my ($sock, $message) = @_;
- logger($message) if ($logging);
+ my ($sock, $logging, $message) = @_;
+ logger($message) if $logging;
print $sock "$message\r\n";
}
sub msg {
- my ($sock, $message) = @_;
- out($sock, "PRIVMSG $chan :$message");
+ my ($sock, $logging, $chan, $message) = @_;
+ out($sock, $logging, "PRIVMSG ${chan} :${message}");
}
sub respond_command {
- my ($sock, $sender_nick, $content) = @_;
+ my ($sock, $logging, $chan, $ball, $hellos, $sender_nick, $content) = @_;
if ($content =~ /\b{wb}([1-9]\d*)?d([1-9]\d*)\b{wb}/) {
# we got dice
my $ndice = $1 // 1;
@@ -94,37 +107,37 @@ sub respond_command {
my $result = $min + int rand(1 + $max - $min);
my $roll = "d${nface}";
$roll = $ndice . $roll if $ndice > 1;
- msg($sock, "${sender_nick} rolled a ${roll} and got ${result}! ${MATA_HAPPY}");
+ msg($sock, $logging, $chan, "${sender_nick} rolled a ${roll} and got ${result}! ${MATA_HAPPY}");
} elsif ($content =~ /\b{wb}pray+\b{wb}/) {
- msg($sock, "Stay prayed up!! ${MATA_CUTE}");
+ msg($sock, $logging, $chan, "Stay prayed up!! ${MATA_CUTE}");
} elsif ($content =~ /\b{wb}bru[hv]+\b{wb}/i) {
- msg($sock, "Did i stutter? ${MATA_NORM}");
+ msg($sock, $logging, $chan, "Did i stutter? ${MATA_NORM}");
} elsif ($content =~ /\b{wb}f+(u?ck+)? *(yo)?u+\b{wb}/i) {
- msg($sock, "$MATA_FLIPOFF");
+ msg($sock, $logging, $chan, "$MATA_FLIPOFF");
} elsif ($content =~ /\b{wb}i+ *l+(ove+)? *(you+|y+|u+)\b{wb}/i) {
- msg($sock, "<3 ${MATA_HAPPY}");
+ msg($sock, $logging, $chan, "<3 ${MATA_HAPPY}");
} elsif ($content =~ /(thanks+|thx+|thank +(yo)?u+)( *a+ *lot+)?/i) {
- msg($sock, "You're welcome, ${sender_nick}! ${MATA_HAPPY}");
+ msg($sock, $logging, $chan, "You're welcome, ${sender_nick}! ${MATA_HAPPY}");
} elsif ($content =~ /who('| +i|)s+ +(a|the)+ +goo+d+ +(bo+[ity]+o*|gir(l+|lie+))/i) {
- msg($sock, "Me! ${MATA_CUTE}");
+ msg($sock, $logging, $chan, "Me! ${MATA_CUTE}");
} elsif ($content =~ /
h(ow+)?\s*(are+|r+)\s*(you+|yo+|u+)(\s+doing+)?(\s+today+)?|
how('|\si|)s+\s+(it+\s+going+|life+|everything+)
/ix) {
- msg($sock, "I feel fantaaaastic... hey, hey, hey! ${MATA_SING}");
+ msg($sock, $logging, $chan, "I feel fantaaaastic... hey, hey, hey! ${MATA_SING}");
} elsif ($content =~ /\b{wb}(
what('|\si|)s+\s*(up+|happening+|cracking+)|
(was)?sup+
)\b{wb}/ix) {
- msg($sock, "Looking for technomage, and you? ${MATA_NORM}");
+ msg($sock, $logging, $chan, "Looking for technomage, and you? ${MATA_NORM}");
} elsif ($content =~ /\b{wb}(
goo+d+\s*(bo+[ity]+o*|gir(l+|lie+))|
w(elcome+)?\s*(b+|back+)
)\b{wb}/ix) {
if ($sender_nick eq $MOTHER) {
- msg($sock, "Thank you, mother! ${MATA_HAPPY}");
+ msg($sock, $logging, $chan, "Thank you, mother! ${MATA_HAPPY}");
} else {
- msg($sock, "Thanks, ${sender_nick}! ${MATA_HAPPY}");
+ msg($sock, $logging, $chan, "Thanks, ${sender_nick}! ${MATA_HAPPY}");
}
} elsif ($content =~ /\b{wb}(
(al+-?)?sala+m+u*\s+alaikum+u*|
@@ -139,34 +152,35 @@ sub respond_command {
well+\s+met+|
yo+
)\b{wb}/ix) {
- msg($sock, "$hellos[int rand($hellos_num)], ${sender_nick}! ${MATA_HAPPY}");
+ msg($sock, $logging, $chan, "$hellos->[rand @$hellos], ${sender_nick}! ${MATA_HAPPY}");
} elsif ($content =~ /\?$/) {
# we got a question
- msg($sock, "$ball[int rand($ball_num)] ${MATA_CUTE}");
+ msg($sock, $logging, $chan, "$ball->[rand @$ball] ${MATA_CUTE}");
} elsif ($sender_nick eq $MOTHER) {
- msg($sock, "Done, mother! ${MATA_HAPPY}");
+ msg($sock, $logging, $chan, "Done, mother! ${MATA_HAPPY}");
} else {
- msg($sock, "\1ACTION leans over and places it's hand near it's antenna. \"HUUH?\" ${MATA_NORM}\1");
+ msg($sock, $logging, $chan, "\1ACTION leans over and places it's hand near it's antenna. \"HUUH?\" ${MATA_NORM}\1");
}
}
sub respond_mention {
- my ($sock, $sender_nick, $message) = @_;
+ my ($sock, $logging, $chan, $quotes, $sender_nick, $message) = @_;
if ($sender_nick eq $MOTHER) {
- msg($sock, "Yes, mother? ${MATA_HAPPY}");
+ msg($sock, $logging, $chan, "Yes, mother? ${MATA_HAPPY}");
} elsif ($message =~ /^\b${NICK_RE}\b$/) {
- msg($sock, "${MATA_NORM} ?");
+ msg($sock, $logging, $chan, "${MATA_NORM} ?");
} else {
- msg($sock, "$quotes[int rand($quotes_num)] ${MATA_NORM}");
+ msg($sock, $logging, $chan, "$quotes->[rand @$quotes] ${MATA_NORM}");
}
}
# respond to channel
# returns 1 if bot shouldn't remember last message for s///, 0 otherwise
sub respond {
- my ($sock, $sender_nick, $message) = @_;
- if ($subbuffer{$sender_nick} && $message =~ m,\b{wb}s/([^/]+)/([^/]*)(?:/([gi]*))?,) {
+ my ($sock, $logging, $chan, $lists, $subbuffer, $sender_nick, $message) = @_;
+ if ($subbuffer->{$sender_nick} && $message =~ m,\b{wb}s/([^/]+)/([^/]*)(?:/([gi]*))?,) {
# chat s///
+ my $prev_message = $subbuffer->{$sender_nick};
my $retext = $1;
my $repl = $2;
my $mods = $3 // '';
@@ -175,93 +189,94 @@ sub respond {
eval {
$regex = qr/(?$imod:$retext)/
};
- return 1 if $@;
+ return $prev_message if $@;
my $ismatch;
if ($mods =~ /g/) {
- $ismatch = $subbuffer{$sender_nick} =~ s/$regex/$repl/g;
+ $ismatch = $prev_message =~ s/$regex/$repl/g;
} else {
- $ismatch = $subbuffer{$sender_nick} =~ s/$regex/$repl/;
+ $ismatch = $prev_message =~ s/$regex/$repl/;
}
if ($ismatch) {
- msg($sock, "${sender_nick} meant to say: $subbuffer{$sender_nick}");
+ msg($sock, $logging, $chan, "${sender_nick} meant to say: ${prev_message}");
}
- return 1;
+ return $prev_message;
} elsif ($message =~ m,watch\?v=([a-zA-Z0-9_-]+),) {
# post youtube video titles from video ID
my $video_id = $1;
my $response = HTTP::Tiny->new->get("https://fuyt.lab8.cz/?s=${video_id}&o=relevance");
unless ($response->{success}) {
- msg($sock, "failed to get title of video ${video_id} ${MATA_DEAD} ($response->{status} $response->{reason}!)");
- return 0;
+ msg($sock, $logging, $chan, "failed to get title of video ${video_id} ${MATA_DEAD} ($response->{status} $response->{reason}!)");
+ return $message;
}
unless (length $response->{content}) {
- msg($sock, "failed to get title of youtube video ${video_id} ${MATA_DEAD} (HTTP response empty!)");
- return 0;
+ msg($sock, $logging, $chan, "failed to get title of youtube video ${video_id} ${MATA_DEAD} (HTTP response empty!)");
+ return $message;
}
my $content = $response->{content};
if ($content =~ m,<span class="title"><a href="https://www\.youtube\.com/watch\?v=$video_id" accesskey="0">([^<]+)</a>,i) {
my $title = $1;
$title =~ tr/[\000\r\n]//d;
$title = trim($title);
- msg($sock, "YouTube: $title");
+ msg($sock, $logging, $chan, "YouTube: $title");
} else {
- msg($sock, "failed to get title of video ${video_id} ${MATA_DEAD} (no videos matching ID $video_id!)");
+ msg($sock, $logging, $chan, "failed to get title of video ${video_id} ${MATA_DEAD} (no videos matching ID $video_id!)");
}
} elsif ($message =~ m,https?://[^ ]+,) {
my $url = $&;
# get in it's HEAD to check if it's text/html
my $response = HTTP::Tiny->new->head($url);
unless ($response->{success}) {
- msg($sock, "$response->{status} $response->{reason} (HEAD ${url}) ${MATA_DEAD} ");
- return 0;
+ msg($sock, $logging, $chan, "$response->{status} $response->{reason} (HEAD ${url}) ${MATA_DEAD}");
+ return $message;
}
unless ($response->{headers}->{'content-type'}) {
- msg($sock, "no content-type header in HTTP response (HEAD ${url}) ${MATA_DEAD}");
- return 0;
+ msg($sock, $logging, $chan, "no content-type header in HTTP response (HEAD ${url}) ${MATA_DEAD}");
+ return $message;
}
unless ($response->{headers}->{'content-type'} =~ m,text/html,) {
# we got a non text/html content type
- msg($sock, "File: $response->{headers}->{'content-type'}");
- return 0;
+ msg($sock, $logging, $chan, "File: $response->{headers}->{'content-type'}");
+ return $message;
}
# if it's text/html, GET it's title
$response = HTTP::Tiny->new->get($url);
unless ($response->{success}) {
- msg($sock, "$response->{status} $response->{reason} (GET ${url}) ${MATA_DEAD}");
- return 0;
+ msg($sock, $logging, $chan, "$response->{status} $response->{reason} (GET ${url}) ${MATA_DEAD}");
+ return $message;
}
unless (length $response->{content}) {
- msg($sock, "Empty HTTP response (GET ${url}) ${MATA_DEAD}");
- return 0;
+ msg($sock, $logging, $chan, "Empty HTTP response (GET ${url}) ${MATA_DEAD}");
+ return $message;
}
my $content = $response->{content};
if ($content =~ m,<title[^>]*>([^<]+)</title[^>]*>,i) {
my $title = $1;
$title =~ tr/[\000\r\n]//d;
$title = trim($title);
- msg($sock, "Title: $title");
+ msg($sock, $logging, $chan, "Title: $title");
} else {
- msg($sock, "No title found (GET ${url} ${MATA_DEAD}");
+ msg($sock, $logging, $chan, "No title found (GET ${url} ${MATA_DEAD}");
}
# TODO: this part should use [^ ] and .* instead but i'm scared of .*
} elsif ($message =~ /^ *${NICK_RE}[:, ] *([^\000\r\n ][^\000\r\n]*)$/
or $message =~ /^ *([^\000\r\n ][^\000\r\n]*)[, ] *${NICK_RE}\W*$/) {
- respond_command($sock, $sender_nick, $1);
+ respond_command($sock, $logging, $chan, $lists->{'ball'}, $lists->{'hellos'}, $sender_nick, $1);
} elsif ($message =~ /\b${NICK_RE}\b/) {
- respond_mention($sock, $sender_nick, $message);
+ respond_mention($sock, $logging, $chan, $lists->{'quotes'}, $sender_nick, $message);
}
- return 0;
+ return $message;
}
sub evasdrop {
- my $sock = shift;
+ my ($sock, $opts, $lists, $subbuffer) = @_;
my $buffer = '';
my $chunk = '';
my $message = '';
+ my %subbuffer;
while (1) {
# buffer up
- if ($tls) {
+ if ($opts->{'tls'}) {
$chunk = <$sock>;
} else {
$sock->recv($chunk, $CHUNK_LENGTH);
@@ -285,48 +300,46 @@ sub evasdrop {
$buffer .= $chunk;
next;
}
-
# log message
- logger($message) if ($logging);
+ logger($message) if ($opts->{'logging'});
# respond to message
if ($message =~ /^PING :([^\000\r\n\ ]+)$/) {
# if we got a ping, pong back
- out($sock, "PONG :$1");
- } elsif ($message =~ /^:([^\000\r\n\#\&\ ][^\000\r\n\ ]*)![^\000\r\n\ ]+@[^\000\r\n\ ]+ PRIVMSG ${chan} :([^\000\r\n]*)$/) {
+ out($sock, $opts->{'logging'}, "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;
- unless (respond($sock, $sender_nick, $sender_message)) {
- $subbuffer{$sender_nick} = $sender_message;
- }
+ $subbuffer->{$sender_nick} = respond($sock, $opts->{'logging'}, $opts->{'chan'}, $lists, $subbuffer, $sender_nick, $sender_message);
}
}
}
while (1) {
# start the connection
+ my ($opts, $lists) = init();
my $sock;
- if ($tls) {
+ if ($opts->{'tls'}) {
$sock = IO::Socket::SSL->new(
Domain => AF_INET,
Type => SOCK_STREAM,
- PeerHost => $host,
- PeerPort => $port,
+ PeerHost => $opts->{'host'},
+ PeerPort => $opts->{'port'},
) || die "Can't open socket: $IO::Socket::errstr";
} else {
$sock = IO::Socket->new(
Domain => AF_INET,
Type => SOCK_STREAM,
- proto => 'tcp',
- PeerHost => $host,
- PeerPort => $port,
+ proto => $opts->{'tcp'},
+ PeerHost => $opts->{'host'},
+ PeerPort => $opts->{'port'},
) || die "Can't open socket: $IO::Socket::errstr";
}
# set user, real, and nick, then join
- out($sock, "USER $USER * * :$REAL");
- out($sock, "NICK $NICK");
- out($sock, "JOIN $chan");
+ out($sock, $opts->{'logging'}, "USER $USER * * :$REAL");
+ out($sock, $opts->{'logging'}, "NICK $NICK");
+ out($sock, $opts->{'logging'}, "JOIN $opts->{'chan'}");
# main loop
- evasdrop($sock);
+ evasdrop($sock, $opts, $lists);
}