#!/usr/bin/env perl
#
# ChivaNet Conversation Bot
# Copyright (C) 2025 Coherent Logic Development LLC
#
# Author: Serena Willis <snw@coherent-logic.com>
#
# Licensed AGPL-3.0
#
# $Log: convobot,v $
# Revision 1.6 2025/02/05 01:03:18 snw
# Fix invite and seen commands to support screen names with spaces
#
# Revision 1.5 2025/02/03 18:14:15 snw
# Further work on bot
#
# Revision 1.4 2025/02/03 17:31:28 snw
# Further MySQL work
#
# Revision 1.3 2025/02/03 15:38:12 snw
# Begin SQL work
#
# Revision 1.2 2025/02/03 04:28:34 snw
# Fix syntax message
#
# Revision 1.1.1.1 2025/02/03 04:22:49 snw
# Initial Commit
#
#
#
use Net::OSCAR;
use Getopt::Long;
use Data::Dumper;
use HTML::Strip;
use DBI;
my $idlemax = 1800;
my $botsn = '';
my $botsrv = '';
my $botpw = '';
my $rasurl = '';
my $dbhost = '';
my $dbname = '';
my $dbusername = '';
my $dbpw = '';
my $dbconn = '';
my $autogreet = 'off';
my $chatroom = '';
my $online = 0;
my $chat_idle_seconds = 0;
my $last_chat_received = time();
my $start_time = time();
my $dbh = '';
my $dsn = '';
my @congregants = ();
$oscar = Net::OSCAR->new();
sub get_seen_status {
my($sn, $chat) = @_;
my $sth = $dbh->prepare("SELECT * FROM seen WHERE aim_server=? AND aim_sn=? AND aim_chatroom=? AND sn=?");
$sth->execute($botsrv, $botsn, $chatroom, $sn);
if($sth->rows > 0) {
my $hashref = $sth->fetchrow_hashref();
$chat->chat_send("I last saw <strong>$sn</strong> on $hashref->{seen_time}.");
}
else {
$chat->chat_send("I have never seen <strong>$sn</strong>.");
}
}
sub update_seen_status {
my($sn) = @_;
my $del = $dbh->prepare("DELETE FROM seen WHERE aim_server=? AND aim_sn=? AND aim_chatroom=? AND sn=?");
$del->execute($botsrv, $botsn, $chatroom, $sn) or die "error DBI->errstr()";
my $ins = $dbh->prepare("INSERT INTO seen (aim_server, aim_sn, aim_chatroom, sn, seen_time) VALUES (?, ?, ?, ?, ?)");
my $seentime = localtime();
$ins->execute($botsrv, $botsn, $chatroom, $sn, $seentime) or die "error DBI->errstr()";
}
sub signon_done {
print "[OK]\n";
print "convobot: joining $chatroom...";
$oscar->chat_join($chatroom, 5);
print "[OK]\n";
$online = 1;
}
sub oscar_error {
my($oscar, $connection, $error, $description, $fatal) = @_;
if($fatal != 0) {
die "\nconvobot: fatal OSCAR error: $description\n";
}
else {
print "\nconvobot: recoverable OSCAR error: $description\n";
}
}
sub chat_joined {
my($oscar, $chatname, $chat) = @_;
print "bot: chat joined [$chatname]\n";
$room = $chat;
bless $room, "Net::OSCAR::Connection::Chat";
print "convobot: connecting to database $dbname\@$dbhost...";
$dsn = "DBI:mysql:database=$dbname;host=$dbhost;port=3306;mysql_connect_timeout=5;";
$dbh = DBI->connect($dsn, $dbusername, $dbpw, {RaiseError => 1});
die "convobot: failed to connect to MySQL database: DBI->errstr()" unless $dbh;
print "[OK]\n";
$oscar->set_callback_chat_buddy_in(\&chat_buddy_in);
$oscar->set_callback_chat_buddy_out(\&chat_buddy_out);
}
sub chat_buddy_in {
my ($oscar, $who, $chat, $buddy) = @_;
update_seen_status($who);
if($who ne $botsn) {
push(@congregants, $who);
print "convobot: [$who] has joined\n";
}
else {
print "convobot: [$who] has joined (ignoring bot)\n";
}
if($autogreet eq "on") {
if(time() - $start_time > 2) {
my @phrases = ('Welcome to [room], [user]! :-)',
'How\'s it going, [user]?',
'Hey [user]! Bring any snacks?',
'Heya [user]! Hope your day is going well!',
'Ooo, [user] has joined [room]! Now the party can start!');
my $phrase = $phrases[rand @phrases];
$phrase =~ s/\[user\]/$who/g;
$phrase =~ s/\[room\]/$chatroom/g;
my $phrasefix = "<div id=convobot></div>$phrase";
$chat->chat_send($phrasefix);
}
else {
print "convobot: not sending greeting for 2 seconds after startup\n";
}
}
}
sub chat_buddy_out {
my ($oscar, $who, $chat) = @_;
my $index = 0;
$index++ until $congregants[$index] eq $who;
splice(@congregants, $index, 1);
print "convobot: $who has left\n";
}
sub chat_im_in {
my($oscar, $who, $chat, $message) = @_;
my $hs = HTML::Strip->new();
my $rawcmd = $hs->parse($message);
my @cmd = split(' ', $rawcmd);
update_seen_status($who);
if($who ne $botsn) {
if($cmd[0] eq "!seen") {
if(exists($cmd[1])) {
my @sna = @cmd[1..$#cmd];
my $ssn = join(' ', @sna);
get_seen_status($ssn, $chat);
}
else {
$chat->chat_send("Syntax: !seen <em>screenname</em>");
}
}
elsif($cmd[0] eq "!speak") {
send_idle_message();
}
elsif($cmd[0] eq "!quote") {
my $fortune = `/usr/games/fortune`;
$room->chat_send($fortune);
}
elsif($cmd[0] eq "!invite") {
if(exists($cmd[1])) {
my @sna = @cmd[1..$#cmd];
my $ssn = join(' ', @sna);
$chat->invite($ssn, "Please join us in $chatroom! <br><em>Requested by $who</em>");
}
}
elsif($cmd[0] eq "!help") {
$room->chat_send("You can enter the following commands:");
$room->chat_send(" <code>!seen <em>screenname</em></code> (find out when <em>screenname</em> was last in the chat)");
$room->chat_send(" <code>!invite <em>screenname</em></code> (invite <em>screenname</em> to the chat)");
$room->chat_send(" <code>!speak</code> (send a random message)");
$room->chat_send(" <code>!quote</code> (send a quote)");
}
}
$last_chat_received = time();
print "convobot: chat received from $who; resetting idle counter\n";
}
sub send_idle_message {
my @phrases = ('Hey [user]! How are you today?',
'I think [user] should bring us some pizza!',
'What\'s everyone up to here?',
'My, what a beautiful day for a chat here in [room]!',
'[user] always has the most interesting things to say.',
'Remember that time [user] was talking here in [room]?',
'What do all you [room] chatters think about pie?',
'[room] seems dead :\'(. That makes me sad! Maybe [user] has something interesting to say?');
my $congregant = $congregants[rand @congregants];
my $phrase = $phrases[rand @phrases];
$phrase =~ s/\[user\]/$congregant/g;
$phrase =~ s/\[room\]/$chatroom/g;
my $phrasefix = "<div id=convobot></div>$phrase";
if(ref($room) eq "Net::OSCAR::Connection::Chat") {
$room->chat_send($phrasefix);
$last_chat_received = time();
}
}
$oscar->set_callback_signon_done(\&signon_done);
$oscar->set_callback_chat_joined(\&chat_joined);
$oscar->set_callback_chat_im_in(\&chat_im_in);
$oscar->set_callback_error(\&oscar_error);
print "ChivaNet Conversation Bot v0.0.1\n";
print " Copyright (C) 2025 Coherent Logic Development LLC\n\n";
GetOptions("aimsn=s" => \$botsn,
"aimhost=s" => \$botsrv,
"aimpw=s" => \$botpw,
"idlemax=s" => \$idlemax,
"chatroom=s" => \$chatroom,
"dbhost=s" => \$dbhost,
"dbname=s" => \$dbname,
"dbusername=s" => \$dbusername,
"dbpw=s" => \$dbpw,
"autogreet=s" => \$autogreet)
or die("error in command line arguments");
%signon = (
screenname => $botsn,
password => $botpw,
host => $botsrv,
);
print "AIM Server: $botsrv\n";
print "AIM Screen Name: $botsn\n";
print "Chat Room: $chatroom\n";
print "DB Host: $dbhost\n";
print "DB Name: $dbname\n";
print "DB Username: $dbusername\n";
print "Idle before ping: $idlemax\n";
print "Auto-Greet: $autogreet\n\n";
print "convobot: attempting to sign in...";
$oscar->signon(%signon);
while(1) {
$oscar->do_one_loop();
$chat_idle_seconds = time() - $last_chat_received;
if($chat_idle_seconds > $idlemax) {
send_idle_message();
}
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>