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