Annotation of ChivanetConvoBot/convobot, revision 1.5

1.1       snw         1: #!/usr/bin/env perl
                      2: 
                      3: # 
                      4: # ChivaNet Conversation Bot 
                      5: #  Copyright (C) 2025 Coherent Logic Development LLC
                      6: #
                      7: # Author: Serena Willis <snw@coherent-logic.com>
                      8: #
                      9: # Licensed AGPL-3.0
                     10: #
1.2       snw        11: # $Log: convobot,v $
1.5     ! snw        12: # Revision 1.4  2025/02/03 17:31:28  snw
        !            13: # Further MySQL work
        !            14: #
1.4       snw        15: # Revision 1.3  2025/02/03 15:38:12  snw
                     16: # Begin SQL work
                     17: #
1.3       snw        18: # Revision 1.2  2025/02/03 04:28:34  snw
                     19: # Fix syntax message
                     20: #
1.2       snw        21: # Revision 1.1.1.1  2025/02/03 04:22:49  snw
                     22: # Initial Commit
                     23: #
1.1       snw        24: #
                     25: #
                     26: 
                     27: use Net::OSCAR;
                     28: use Getopt::Long;
                     29: use Data::Dumper;
                     30: use HTML::Strip;
1.3       snw        31: use DBI;
1.1       snw        32: 
                     33: my $idlemax = 1800;
                     34: my $botsn = '';
                     35: my $botsrv = '';
                     36: my $botpw = '';
                     37: my $rasurl = '';
1.3       snw        38: 
                     39: my $dbhost = '';
                     40: my $dbname = '';
                     41: my $dbusername = '';
                     42: my $dbpw = '';
                     43: my $dbconn = '';
1.4       snw        44: my $autogreet = 'off';
1.3       snw        45: 
1.1       snw        46: my $chatroom = '';
                     47: my $online = 0;
                     48: my $chat_idle_seconds = 0;
                     49: my $last_chat_received = time();
                     50: my $start_time = time();
1.5     ! snw        51: my $dbh = '';
        !            52: my $dsn = '';
1.1       snw        53: 
                     54: my @congregants = ();
                     55: 
1.4       snw        56: $oscar = Net::OSCAR->new();
                     57: 
                     58: sub get_seen_status {
                     59:     my($sn, $chat) = @_;
1.1       snw        60: 
1.4       snw        61:     my $sth = $dbh->prepare("SELECT * FROM seen WHERE aim_server=? AND aim_sn=? AND aim_chatroom=? AND sn=?");
                     62:     $sth->execute($botsrv, $botsn, $chatroom, $sn);
1.1       snw        63: 
1.4       snw        64:     if($sth->rows > 0) {
                     65:         my $hashref = $sth->fetchrow_hashref();
                     66:         $chat->chat_send("I last saw <strong>$sn</strong> on $hashref->{seen_time}.");
                     67:     }
                     68:     else {
                     69:         $chat->chat_send("I have never seen <strong>$sn</strong>.");
                     70:     }
                     71:     
                     72: }
1.3       snw        73: 
1.4       snw        74: sub update_seen_status {
1.3       snw        75:     my($sn) = @_;
                     76: 
1.4       snw        77:     my $del = $dbh->prepare("DELETE FROM seen WHERE aim_server=? AND aim_sn=? AND aim_chatroom=? AND sn=?");
1.5     ! snw        78:     $del->execute($botsrv, $botsn, $chatroom, $sn) or die "error DBI->errstr()";
1.3       snw        79:     
1.5     ! snw        80:     my $ins = $dbh->prepare("INSERT INTO seen (aim_server, aim_sn, aim_chatroom, sn, seen_time) VALUES (?, ?, ?, ?, ?)");
        !            81:     my $seentime = localtime();
        !            82:     $ins->execute($botsrv, $botsn, $chatroom, $sn, $seentime) or die "error DBI->errstr()";        
1.3       snw        83: }
1.1       snw        84: 
                     85: sub signon_done {
                     86:     print "[OK]\n";
1.5     ! snw        87:     print "convobot:  joining $chatroom...";
        !            88:     $oscar->chat_join($chatroom, 5);   
1.4       snw        89:     print "[OK]\n";
1.1       snw        90:     $online = 1;
                     91: }
                     92: 
1.4       snw        93: sub oscar_error {
                     94:     my($oscar, $connection, $error, $description, $fatal) = @_;
                     95: 
                     96:     if($fatal != 0) {
1.5     ! snw        97:         die "\nconvobot:  fatal OSCAR error:  $description\n";        
1.4       snw        98:     }
                     99:     else {
1.5     ! snw       100:         print "\nconvobot:  recoverable OSCAR error: $description\n";
1.4       snw       101:     }
                    102:    
                    103: }
                    104: 
1.1       snw       105: sub chat_joined {
                    106:     my($oscar, $chatname, $chat) = @_;
                    107: 
1.5     ! snw       108:     print "bot:  chat joined [$chatname]\n";
        !           109:     
1.1       snw       110:     $room = $chat;
                    111:     bless $room, "Net::OSCAR::Connection::Chat";
1.5     ! snw       112: 
        !           113:     print "convobot:  connecting to database $dbname\@$dbhost...";
        !           114: 
        !           115:     $dsn = "DBI:mysql:database=$dbname;host=$dbhost;port=3306;mysql_connect_timeout=5;";
        !           116:     $dbh = DBI->connect($dsn, $dbusername, $dbpw, {RaiseError => 1});
        !           117:     die "convobot:  failed to connect to MySQL database: DBI->errstr()" unless $dbh;
        !           118: 
        !           119:     print "[OK]\n";
        !           120: 
        !           121:     $oscar->set_callback_chat_buddy_in(\&chat_buddy_in);
        !           122:     $oscar->set_callback_chat_buddy_out(\&chat_buddy_out);
1.1       snw       123: }
                    124: 
                    125: sub chat_buddy_in {
                    126:     my ($oscar, $who, $chat, $buddy) = @_;
                    127: 
1.4       snw       128:     update_seen_status($who);
1.1       snw       129:     
                    130:     if($who ne $botsn) {
                    131:         push(@congregants, $who);
1.5     ! snw       132:         print "convobot:  [$who] has joined\n";        
1.1       snw       133:     }
                    134:     else {
1.5     ! snw       135:         print "convobot:  [$who] has joined (ignoring bot)\n";
1.1       snw       136:     }
                    137: 
1.4       snw       138: 
                    139:     if($autogreet eq "on") {
                    140:         if(time() - $start_time > 2) {
                    141:             my @phrases = ('Welcome to [room], [user]! :-)',
                    142:                            'How\'s it going, [user]?',
                    143:                            'Hey [user]! Bring any snacks?',
                    144:                            'Heya [user]! Hope your day is going well!',
                    145:                            'Ooo, [user] has joined [room]! Now the party can start!');
                    146:             
                    147:             my $phrase = $phrases[rand @phrases];
                    148:             $phrase =~ s/\[user\]/$who/g;
                    149:             $phrase =~ s/\[room\]/$chatroom/g;       
                    150:             my $phrasefix = "<div id=convobot></div>$phrase";
                    151:             $chat->chat_send($phrasefix);
                    152:         }
                    153:         else {
1.5     ! snw       154:             print "convobot:  not sending greeting for 2 seconds after startup\n";
1.4       snw       155:         }
1.1       snw       156:     }
                    157: }
                    158: 
                    159: sub chat_buddy_out {
                    160:     my ($oscar, $who, $chat) = @_;
                    161:     my $index = 0;
                    162: 
                    163:     $index++ until $congregants[$index] eq $who;
                    164:     splice(@congregants, $index, 1);
                    165: 
1.5     ! snw       166:     print "convobot:  $who has left\n";
1.1       snw       167: }
                    168: 
                    169: sub chat_im_in {
                    170:     my($oscar, $who, $chat, $message) = @_;
                    171: 
                    172:     my $hs = HTML::Strip->new();
                    173:     my $rawcmd = $hs->parse($message);
                    174:     my @cmd = split(' ', $rawcmd);
                    175: 
1.4       snw       176:     update_seen_status($who);
1.5     ! snw       177: 
        !           178:     if($who ne $botsn) {
        !           179:        if($cmd[0] eq "!seen") {
        !           180:            if(exists($cmd[1])) {
        !           181:                get_seen_status($cmd[1], $chat);
        !           182:            }
        !           183:            else {
        !           184:                $chat->chat_send("Syntax: !seen <em>screenname</em>");
        !           185:            }
        !           186:        }
        !           187:        elsif($cmd[0] eq "!speak") {
        !           188:            send_idle_message();
        !           189:        }
        !           190:        elsif($cmd[0] eq "!quote") {
        !           191:            my $fortune = `/usr/games/fortune`;
        !           192:            $room->chat_send($fortune);
        !           193:        }
        !           194:        elsif($cmd[0] eq "!help") {
        !           195:            $room->chat_send("You can enter the following commands:");
        !           196:            $room->chat_send(" <code>!seen <em>screenname</em></code>  (find out when <em>screenname</em> was last in the chat)");
        !           197:            $room->chat_send(" <code>!speak</code>  (send a random message)");
        !           198:            $room->chat_send(" <code>!quote</code>  (send a quote)");       
        !           199:        }
1.1       snw       200:     }
1.5     ! snw       201:        
        !           202:                 
1.1       snw       203:     $last_chat_received = time();    
                    204: 
1.5     ! snw       205:     print "convobot:  chat received from $who; resetting idle counter\n";
1.1       snw       206:     
                    207: }
                    208: 
                    209: sub send_idle_message {
                    210: 
                    211:     my @phrases = ('Hey [user]! How are you today?',
                    212:                    'I think [user] should bring us some pizza!',
                    213:                    'What\'s everyone up to here?',
                    214:                    'My, what a beautiful day for a chat here in [room]!',
                    215:                    '[user] always has the most interesting things to say.',
                    216:                    'Remember that time [user] was talking here in [room]?',
                    217:                    'What do all you [room] chatters think about pie?',
                    218:                    '[room] seems dead :\'(. That makes me sad! Maybe [user] has something interesting to say?');
                    219: 
                    220:     my $congregant = $congregants[rand @congregants];
                    221:     my $phrase = $phrases[rand @phrases];
                    222:     $phrase =~ s/\[user\]/$congregant/g;
                    223:     $phrase =~ s/\[room\]/$chatroom/g;    
                    224:     my $phrasefix = "<div id=convobot></div>$phrase";
                    225:     
                    226:     if(ref($room) eq "Net::OSCAR::Connection::Chat") {
                    227:         $room->chat_send($phrasefix);
                    228:         $last_chat_received = time();
                    229:     }
1.5     ! snw       230:     
1.1       snw       231: }
                    232: 
                    233: $oscar->set_callback_signon_done(\&signon_done);
                    234: $oscar->set_callback_chat_joined(\&chat_joined);
                    235: $oscar->set_callback_chat_im_in(\&chat_im_in);
1.4       snw       236: $oscar->set_callback_error(\&oscar_error);
1.1       snw       237: 
                    238: print "ChivaNet Conversation Bot v0.0.1\n";
                    239: print " Copyright (C) 2025 Coherent Logic Development LLC\n\n";
                    240: 
1.4       snw       241: GetOptions("aimsn=s" => \$botsn,
                    242:            "aimhost=s" => \$botsrv,
                    243:            "aimpw=s" => \$botpw,
                    244:            "idlemax=s" => \$idlemax,
                    245:            "chatroom=s" => \$chatroom,
                    246:            "dbhost=s" => \$dbhost,
                    247:            "dbname=s" => \$dbname,
                    248:            "dbusername=s" => \$dbusername,
                    249:            "dbpw=s" => \$dbpw,
                    250:            "autogreet=s" => \$autogreet)
                    251:     or die("error in command line arguments");
                    252: 
                    253: %signon = (
                    254:     screenname => $botsn,
                    255:     password => $botpw,
                    256:     host => $botsrv,
                    257: ); 
                    258: 
                    259: print "AIM Server:        $botsrv\n";
                    260: print "AIM Screen Name:   $botsn\n";
                    261: print "Chat Room:         $chatroom\n";
                    262: print "DB Host:           $dbhost\n";
                    263: print "DB Name:           $dbname\n";
                    264: print "DB Username:       $dbusername\n";
                    265: print "Idle before ping:  $idlemax\n";
                    266: print "Auto-Greet:        $autogreet\n\n";
                    267: 
                    268: 
1.5     ! snw       269: print "convobot:  attempting to sign in...";
1.1       snw       270: $oscar->signon(%signon);
                    271: 
                    272: while(1) {
                    273:     $oscar->do_one_loop();
                    274:     $chat_idle_seconds = time() - $last_chat_received;
                    275:     
                    276:     if($chat_idle_seconds > $idlemax) {
                    277:         send_idle_message();
                    278:     }
                    279: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>