================================================================================ Challenge Let's take our original Auto Greet bot and turn it into a simple Chat bot. The goal is to make the chat seem realistic in order to trick a user into thinking that a real human is talking with him. As part of this task, we will add new replies for when a user joins a channel, parts a channel, changes the topic, and says something. Just like the way we handled notices, we should use one reply at random. We will remove the nick_change subroutine because stealing someone's old nick is annoying. When a user says something, we will search for the keywords in the chat and repeat it back to the user, to pretend like the bot is listening. ================================================================================ Modifying greetbot.pl Once again, we're going to change the name of GreetBot to ChatBot. Here is the diff: --- /home/perl102/autogreet.pl Sun Aug 29 06:53:39 2021 +++ /home/perl103/chatbot.pl Sun Aug 29 07:11:13 2021 @@ -2,9 +2,12 @@ use strict; use warnings; -package GreetBot; +package ChatBot; use base qw(Bot::BasicBot); +use Lingua::EN::Tagger; +my $logs; A diff is a short way of showing what changed in a file. The + plus symbol at the left of the screen means a line was added, and the - minus symbol at the left of the screen means that a line was deleted. First, we delete the line with GreetBot and replace it with ChatBot. Next, we add a new line: use Lingua::EN::Tagger. This loads a new module, Lingua::EN::Tagger, to help us recognize the parts of speech in a sentence. See: https://metacpan.org/pod/Lingua::EN::Tagger This module comes from CPAN, the Comprehensive Perl Archive Network. CPAN is similar to other package managers like npm from Node.js or pip from python. It contains an enormous collection of perl modules that you can use. See: http://www.cpan.org Lingua::EN::Tagger helps us easily find the noun phrases of a sentence. These noun phrases are the keywords that our bot will repeat back to pretend like it is listening. For example, in the sentence: Some of the monks at the Perl monastery observe a vow of silence. 'Some of the monks', 'the Perl monastery', and 'a vow of silence' are noun phrases. Next, we declare the variable $logs. Notice that we declare $logs outside of any subroutine. This is necessary because we want $logs to accumulate all user chat from the moment the bot connects. In perl, a variable declared with my is a *lexical* variable. If a variable is declared inside a subroutine, it exists only from the opening brace { to the closing brace }. Once the subroutine ends, lexical variables are recycled and their data is lost forever. For example, suppose we have: sub said { my $logs = "12:00 < nickname> Welcome, user!\n" } print $logs; Nothing will get printed, because $logs would cease to exist by the time the program leaves the end brace }. We need $logs to survive after leaving a subroutine, so we define it outside of the subroutine. We're going to modify our chanjoin subroutine to add some new greetings: sub chanjoin { my $self = shift; my $arguments = shift; @@ -12,18 +15,34 @@ if ($nick eq $self->pocoirc->nick_name()) { return; } + my @greetings = ("Hey there, $nick!", + "$nick, welcome!", + "sup $nick!", + "$nick, it's good to see you.", + "How can I help you, $nick?", + "Hey $nick, do you hang out here too?", + "Hiya $nick."); + $self->say( channel => $arguments->{channel}, - body => "Welcome, $nick!", + body => $greetings[int(rand(scalar(@greetings)))], ); } We again create an array of greetings. In $self->say(), we pick a random greeting: body => $greetings[int(rand(scalar(@greetings)))], First, we find the length of the array @greetings using scalar(@greetings). Then, we select a random number between 0 and the length of the array with rand(scalar(@greetings)). In this case, the array has a length of 7, but we don't want to write rand(7). This is because we might later want to add or remove greetings, so the length of the array may change. Besides, we might forget to update the number 7. We then *truncate* the number (drop the decimal part) with int(). We now have a random number between zero to less than the length of the array. We use this number as an index into the array @greetings. This gives us $greetings[int(rand(scalar(@greetings)))]. Notice that we change from an array sigil @ to a scalar sigil $ because we want one greeting, a string, instead of an array of strings. We do the same with chanpart: sub chanpart { my $self = shift; my $arguments = shift; + my $nick = $arguments->{who}; + my @farewells = ("I'm sad to see $nick go", + "Oh, $nick left, I was just about to send a message.", + "I always seem to return just as $nick leaves.", + "I hope $nick will rejoin later.", + "I'm going to take a break too, brb.", + "See you later $nick. Oops, I was too late."); + $self->say( channel => $arguments->{channel}, - body => "I'm sad to see $arguments->{who} go.", + body => $farewells[int(rand(scalar(@farewells)))], ); } In our old noticed subroutine, we hard-coded the number 4 to represent the length of the array. As mentioned above, this is not ideal. So we use scalar(@notices) to determine the length of the array: @@ -53,39 +72,50 @@ $self->notice( who => $nick, channel => $arguments->{channel}, - body => $notices[int(rand(4))], + body => $notices[int(rand(scalar(@notices)))], ); } We modify the topic subroutine to send different replies: sub topic { my $self = shift; my $arguments = shift; + my @replies = ("Nice", + "Hm, I liked the old topic better.", + "Please don't change the topic.", + "Good thinking.", + "That makes more sense."); - if ($arguments->{who} eq $self->pocoirc->nick_name()) { - return; - } - $self->pocoirc->yield('topic' => $arguments->{channel} => "$arguments->{topic} || Don't change the topic!"); + $self->say( + channel => $arguments->{channel}, + body => $replies[int(rand(scalar(@replies)))], + ); } We'll delete the nick_change subroutine and add a said subroutine: -sub nick_change { - my $self = shift; - my $oldnick = shift; - my $newnick = shift; - - if ($newnick eq $self->pocoirc->nick_name()) { - return; - } - - $self->pocoirc->yield('nick' => "$oldnick"); - $self->say( - who => "$newnick", - body => "If you don't mind, I'd like to use your old nick.", - ); -} +sub said { + my $self = shift; + my $arguments = shift; + + $logs .= "$arguments->{body}\n"; + my $p = new Lingua::EN::Tagger; + my %word_freqs = $p->get_words($logs); + my $keyword; + my $total = 0; + foreach my $freq (keys %word_freqs) { + $total += $word_freqs{$freq}; + $keyword = $freq if rand($total) < $word_freqs{$freq}; + } + my @replies = ("I think you have a valid point about $keyword.", + "Hm, what do others think about $keyword?", + ucfirst $keyword." is not something I'm familiar with", + "Are you sure about $keyword?", + "Tell me more about $keyword.", + "What about $keyword?", + "Let's talk about something else besides $keyword."); + return $replies[int(rand(scalar(@replies)))]; +} At the bottom of the file, we replace GreetBot->new( with ChatBot->new(: package main; -my $bot = GreetBot->new( +my $bot = ChatBot->new( server => 'irc.example.com', port => '6667', channels => ['#perl102'], (Hint: the answer is in /home/perl103/chatbot.pl) This is a very simple bot, but perhaps in the future, you could use more advanced techniques to write a more realistic chat bot. ================================================================================ Username: perl103 Password: t3Qa8CRfArL Server: freeirc.org Port: 22 ================================================================================