]> jfr.im git - irc/SurrealServices/srsv.git/blame - branches/0.4.3/modules/serviceslibs/botserv.pm
sucky code for /cs set noclones; /cs tempban; /cs set bantime don't expect it to...
[irc/SurrealServices/srsv.git] / branches / 0.4.3 / modules / serviceslibs / botserv.pm
CommitLineData
7b261bb8 1# This file is part of SurrealServices.
2#
3# SurrealServices is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# SurrealServices is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with SurrealServices; if not, write to the Free Software
15# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16package botserv;
17
18use strict;
19no strict 'refs';
20
21use Safe;
22
23use SrSv::Agent;
24use SrSv::Process::Worker 'ima_worker'; #FIXME
25
26use SrSv::Text::Format qw(columnar);
27use SrSv::Errors;
28
29use SrSv::Conf2Consts qw( main services );
30
920a4abc 31use SrSv::User qw(get_user_nick get_user_id :flood);
7b261bb8 32use SrSv::User::Notice;
33use SrSv::Help qw( sendhelp );
34
35use SrSv::ChanReg::Flags;
36use SrSv::NickReg::Flags qw(NRF_NOHIGHLIGHT nr_chk_flag_user);
37
38use SrSv::MySQL '$dbh';
39
40use constant {
41 F_PRIVATE => 1,
42 F_DEAF => 2
43};
44
45our $bsnick_default = 'BotServ';
46our $bsnick = $bsnick_default;
47our $botchmode;
48if(!ircd::PREFIXAQ_DISABLE()) {
49 $botchmode = '+q';
50} else {
51 $botchmode = '+qo';
52}
53
54*agent = \&chanserv::agent;
55
56our $calc_safe = new Safe;
57
58our (
59 $get_all_bots, $get_botchans, $get_botstay_chans, $get_chan_bot, $get_bots_chans, $get_bot_info,
60
61 $create_bot, $delete_bot, $delete_bot_allchans, $assign_bot, $unassign_bot,
62 $change_bot, $update_chanreg_bot,
63
64 $is_bot, $has_bot,
65
66 $set_flag, $unset_flag, $get_flags
67);
68
69sub init() {
70 $get_all_bots = $dbh->prepare("SELECT nick, ident, vhost, gecos, flags FROM bot");
71 $get_botchans = $dbh->prepare("SELECT chan, COALESCE(bot, '$chanserv::csnick') FROM chanreg WHERE bot != '' OR (flags & ". CRF_BOTSTAY() . ")");
72 $get_botstay_chans = $dbh->prepare("SELECT chan, COALESCE(bot, '$chanserv::csnick') FROM chanreg WHERE (flags & ".
73 CRF_BOTSTAY() . ")");
74 $get_chan_bot = $dbh->prepare("SELECT bot FROM chanreg WHERE chan=?");
75 $get_bots_chans = $dbh->prepare("SELECT chan FROM chanreg WHERE bot=?");
76 $get_bot_info = $dbh->prepare("SELECT nick, ident, vhost, gecos, flags FROM bot WHERE nick=?");
77
78 $create_bot = $dbh->prepare("INSERT INTO bot SET nick=?, ident=?, vhost=?, gecos=?");
79 $delete_bot = $dbh->prepare("DELETE FROM bot WHERE nick=?");
80 $delete_bot_allchans = $dbh->prepare("UPDATE chanreg SET bot='' WHERE bot=?");
81 $change_bot = $dbh->prepare("UPDATE bot SET nick=?, ident=?, vhost=?, gecos=? WHERE nick=?");
82 $update_chanreg_bot = $dbh->prepare("UPDATE chanreg SET bot=? WHERE bot=?");
83
84 $assign_bot = $dbh->prepare("UPDATE chanreg, bot SET chanreg.bot=bot.nick WHERE bot.nick=? AND chan=?");
85 $unassign_bot = $dbh->prepare("UPDATE chanreg SET chanreg.bot='' WHERE chan=?");
86
87 $is_bot = $dbh->prepare("SELECT 1 FROM bot WHERE nick=?");
88 $has_bot = $dbh->prepare("SELECT 1 FROM chanreg WHERE chan=? AND bot != ''");
89
90 $set_flag = $dbh->prepare("UPDATE bot SET flags=(flags | (?)) WHERE nick=?");
91 $unset_flag = $dbh->prepare("UPDATE bot SET flags=(flags & ~(?)) WHERE nick=?");
92 $get_flags = $dbh->prepare("SELECT flags FROM bot WHERE bot.nick=?");
93
94 register() unless ima_worker; #FIXME
95};
96
97sub dispatch($$$) {
98 my ($src, $dst, $msg) = @_;
99
100 if(lc $dst eq lc $bsnick or lc $dst eq lc $bsnick_default ) {
101 bs_dispatch($src, $dst, $msg);
102 }
103 elsif($dst =~ /^#/) {
104 if($msg =~ /^\!/) {
105 $has_bot->execute($dst);
106 return unless($has_bot->fetchrow_array);
107 chan_dispatch($src, $dst, $msg);
108 } else {
109 chan_msg($src, $dst, $msg);
110 }
111 }
112 else {
113 $is_bot->execute($dst);
114 if($is_bot->fetchrow_array) {
115 bot_dispatch($src, $dst, $msg);
116 }
117 }
118}
119
120### BOTSERV COMMANDS ###
121
122sub bs_dispatch($$$) {
123 my ($src, $dst, $msg) = @_;
124 $msg =~ s/^\s+//;
125 my @args = split(/\s+/, $msg);
126 my $cmd = shift @args;
127
8a142680 128 my $user = { NICK => $src, AGENT => $dst };
7b261bb8 129
920a4abc 130 return if flood_check($user);
7b261bb8 131
132 if($cmd =~ /^assign$/i) {
133 if (@args == 2) {
134 bs_assign($user, {CHAN => $args[0]}, $args[1]);
135 } else {
136 notice($user, 'Syntax: ASSIGN <#channel> <bot>');
137 }
138 }
139 elsif ($cmd =~ /^unassign$/i) {
140 if (@args == 1) {
141 bs_assign($user, {CHAN => $args[0]}, '');
142 } else {
143 notice($user, 'Syntax: UNASSIGN <#channel>');
144 }
145 }
146 elsif ($cmd =~ /^list$/i) {
147 if(@args == 0) {
148 bs_list($user);
149 } else {
150 notice($user, 'Syntax: LIST');
151 }
152 }
153 elsif ($cmd =~ /^add$/i) {
154 if (@args >= 4) {
155 @args = split(/\s+/, $msg, 5);
156 bs_add($user, $args[1], $args[2], $args[3], $args[4]);
157 } else {
158 notice($user, 'Syntax: ADD <nick> <ident> <vhost> <realname>');
159 }
160 }
161 elsif ($cmd =~ /^change$/i) {
162 if (@args >= 4) {
163 @args = split(/\s+/, $msg, 6);
164 bs_change($user, $args[1], $args[2], $args[3], $args[4], $args[5]);
165 } else {
166 notice($user, 'Syntax: ADD <oldnick> <nick> <ident> <vhost> <realname>');
167 }
168 }
169 elsif ($cmd =~ /^del(ete)?$/i) {
170 if (@args == 1) {
171 bs_del($user, $args[0]);
172 } else {
173 notice($user, 'Syntax: DEL <botnick>');
174 }
175 }
176 elsif($cmd =~ /^set$/i) {
177 if(@args == 3) {
178 bs_set($user, $args[0], $args[1], $args[2]);
179 } else {
180 notice($user, 'Syntax: SET <botnick> <option> <value>');
181 }
182 }
183 elsif($cmd =~ /^seen$/i) {
184 if(@args >= 1) {
185 nickserv::ns_seen($user, @args);
186 } else {
187 notice($user, 'Syntax: SEEN <nick> [nick ...]');
188 }
189 }
190
191 elsif($cmd =~ /^(say|act)$/i) {
192 if(@args > 1) {
193 my @args = split(/\s+/, $msg, 3);
194 my $botmsg = $args[2];
195 $botmsg = "\001ACTION $botmsg\001" if(lc $cmd eq 'act');
196 bot_say($user, {CHAN => $args[1]}, $botmsg);
197 } else {
198 notice($user, 'Syntax: '.uc($cmd).' <#chan> <message>');
199 }
200 }
201 elsif($cmd =~ /^info$/i) {
202 if(@args == 1) {
203 bs_info($user, $args[0]);
204 } else {
205 notice($user, 'Syntax: INFO <botnick>');
206 }
207 }
208 elsif($cmd =~ /^help$/i) {
209 sendhelp($user, 'botserv', @args);
210 }
211 elsif($cmd =~ /^d(ice)?$/i) {
212 notice($user, get_dice($args[0]));
213 }
214 else {
215 notice($user, "Unrecognized command. For help, type: \002/bs help\002");
216 }
217}
218
219# For unassign, set $bot to ''
220#
221sub bs_assign($$$) {
222 my ($user, $chan, $bot) = @_;
223
224 chanserv::chk_registered($user, $chan) or return;
225
674d6fee 226 unless (chanserv::can_do($chan, 'BotAssign', $user)) {
7b261bb8 227 notice($user, $err_deny);
228 return;
229 }
230
231 if ($bot) {
232 $is_bot->execute($bot);
233 unless($is_bot->fetchrow_array) {
234 notice($user, "\002$bot\002 is not a bot.");
235 return;
236 }
237 }
238
239 $get_flags->execute($bot);
240 my ($botflags) = $get_flags->fetchrow_array;
241 if (($botflags & F_PRIVATE) && !adminserv::can_do($user, 'BOT')) {
242 notice($user, $err_deny);
243 return;
244 }
245
246
247 my $cn = $chan->{CHAN};
248 my $src = get_user_nick($user);
249 my $oldbot;
250 if ($oldbot = get_chan_bot($chan)) {
251 agent_part($oldbot, $cn, "Unassigned by \002$src\002.");
252 }
253
254
255
256 if($bot) {
257 $assign_bot->execute($bot, $cn);
258 bot_join($chan, $bot);
259 notice($user, "\002$bot\002 now assigned to \002$cn\002.");
260 } else {
261 $unassign_bot->execute($cn);
262 notice($user, "\002$oldbot\002 removed from \002$cn\002.");
263 }
264}
265
266sub bs_list($) {
267 my ($user) = @_;
268 my @data;
269 my $is_oper = adminserv::is_svsop($user, adminserv::S_HELP());
270
271 $get_all_bots->execute();
272 while (my ($botnick, $botident, $bothost, $botgecos, $flags) = $get_all_bots->fetchrow_array) {
273 if($is_oper) {
274 push @data, [$botnick, "($botident\@$bothost)", $botgecos,
275 (($flags & F_PRIVATE) ? "Private":"Public")];
276 } else {
277 next if($flags & F_PRIVATE);
278 push @data, [$botnick, "($botident\@$bothost)", $botgecos];
279 }
280 }
281
282 notice($user, columnar({TITLE => "The following bots are available:",
283 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data));
284}
285
286sub bs_add($$$$$) {
287 my ($user, $botnick, $botident, $bothost, $botgecos) = @_;
288
289 unless (adminserv::can_do($user, 'BOT')) {
290 notice($user, $err_deny);
291 return;
292 }
293
294 if (my $ret = is_invalid_agentname($botnick, $botident, $bothost)) {
295 notice($user, $ret);
296 return;
297 }
298
299 if(nickserv::is_registered($botnick)) {
300 notice($user, "The nick \002$botnick\002 is already registered.");
301 return;
302 }
303
304 if(nickserv::is_online($botnick)) {
305 notice($user, "The nick \002$botnick\002 is currently in use.");
306 return;
307 }
308
309 $is_bot->execute($botnick);
310 if($is_bot->fetchrow_array) {
311 notice($user, "\002$botnick\002 already exists.");
312 return;
313 }
314
315 $create_bot->execute($botnick, $botident, $bothost, $botgecos);
316 ircd::sqline($botnick, $services::qlreason);
317 agent_connect($botnick, $botident, $bothost, '+pqBSrz', $botgecos);
318 agent_join($botnick, main_conf_diag);
319 ircd::setmode($main::rsnick, main_conf_diag, '+h', $botnick);
320
321 notice($user, "Bot $botnick connected.");
322}
323
324sub bs_del($$) {
325 my ($user, $botnick) = @_;
326
327 unless (adminserv::can_do($user, 'BOT')) {
328 notice($user, $err_deny);
329 return;
330 }
331 $is_bot->execute($botnick);
332 if (!$is_bot->fetchrow_array) {
333 notice($user, "\002$botnick\002 is not a bot.");
334 return;
335 }
336
337 my $src = get_user_nick($user);
338 $delete_bot->execute($botnick);
339 agent_quit($botnick, "Deleted by \002$src\002.");
340 ircd::unsqline($botnick);
341
342 $delete_bot_allchans->execute($botnick);
343 notice($user, "Bot \002$botnick\002 disconnected.");
344}
345
346sub bs_set($$$$) {
347 my ($user, $botnick, $set, $parm) = @_;
348
349 unless (adminserv::can_do($user, 'BOT')) {
350 notice($user, $err_deny);
351 return;
352 }
353 if($set =~ /^private$/i) {
354 if ($parm =~ /^(on|true)$/i) {
355 set_flag($botnick, F_PRIVATE());
356 notice($user, "\002$botnick\002 is now private.");
357 }
358 elsif ($parm =~ /^(off|false)$/i) {
359 unset_flag($botnick, F_PRIVATE());
360 notice($user, "\002$botnick\002 is now public.");
361 }
362 else {
363 notice($user, 'Syntax: SET <botnick> PRIVATE <ON|OFF>');
364 }
365 }
366 if($set =~ /^deaf$/i) {
367 if ($parm =~ /^(on|true)$/i) {
368 set_flag($botnick, F_DEAF());
369 setagent_umode($botnick, '+d');
370 notice($user, "\002$botnick\002 is now deaf.");
371 }
372 elsif ($parm =~ /^(off|false)$/i) {
373 unset_flag($botnick, F_DEAF());
374 setagent_umode($botnick, '-d');
375 notice($user, "\002$botnick\002 is now undeaf.");
376 }
377 else {
378 notice($user, 'Syntax: SET <botnick> DEAF <ON|OFF>');
379 }
380 }
381}
382
383sub bs_info($$) {
384 my ($user, $botnick) = @_;
385
386 unless (adminserv::can_do($user, 'HELP')) {
387 notice($user, $err_deny);
388 return;
389 }
390 $is_bot->execute($botnick);
391 unless($is_bot->fetchrow_array) {
392 notice($user, "\002$botnick\002 is not a bot.");
393 return;
394 }
395
396 $get_bot_info->execute($botnick);
397 my ($nick, $ident, $vhost, $gecos, $flags) = $get_bot_info->fetchrow_array;
398 $get_bot_info->finish();
399 $get_bots_chans->execute($botnick);
400 my @chans = ();
401 while (my $chan = $get_bots_chans->fetchrow_array) {
402 push @chans, $chan;
403 }
404 $get_bots_chans->finish();
405
406 notice($user, columnar({TITLE => "Information for bot \002$nick\002:",
407 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)},
408 ['Mask:', "$ident\@$vhost"], ['Realname:', $gecos],
409 ['Flags:', (($flags & F_PRIVATE())?'Private ':'').(($flags & F_DEAF())?'Deaf ':'')],
410 {COLLAPSE => [
411 'Assigned to '. @chans.' channel(s):',
412 ' ' . join(' ', @chans)
413 ]}
414 ));
415}
416
417sub bs_change($$$$$$) {
418 my ($user, $oldnick, $botnick, $botident, $bothost, $botgecos) = @_;
419
420 if (lc $oldnick eq lc $botnick) {
421 notice($user, "Error: $oldnick is the same (case-insensitive) as $botnick",
422 "At this time, you cannot change only the ident, host, gecos, or nick-case of a bot.");
423 return;
424 }
425
426 unless (adminserv::can_do($user, 'BOT')) {
427 notice($user, $err_deny);
428 return;
429 }
430
431 if (my $ret = is_invalid_agentname($botnick, $botident, $bothost)) {
432 notice($user, $ret);
433 return;
434 }
435
436 if(nickserv::is_registered($botnick)) {
437 notice($user, "The nick \002$botnick\002 is already registered.");
438 return;
439 }
440
441 if(nickserv::is_online($botnick)) {
442 notice($user, "The nick \002$botnick\002 is currently in use.");
443 return;
444 }
445
446 $is_bot->execute($botnick);
447 if($is_bot->fetchrow_array) {
448 notice($user, "\002$botnick\002 already exists.");
449 return;
450 }
451
452 #Create bot first, join it to its chans
453 # then finally delete the old bot
454 # This is to prevent races.
455 $create_bot->execute($botnick, $botident, $bothost, $botgecos);
456 ircd::sqline($botnick, $services::qlreason);
457 agent_connect($botnick, $botident, $bothost, '+pqBSrz', $botgecos);
458 agent_join($botnick, main_conf_diag);
459 ircd::setmode($main::rsnick, main_conf_diag, '+h', $botnick);
460
461 notice($user, "Bot $botnick connected.");
462
463 $get_bots_chans->execute($oldnick);
464 while(my ($cn) = $get_bots_chans->fetchrow_array()) {
465 my $chan = { CHAN => $cn };
466 bot_join($chan, $botnick)
467 if chanserv::get_user_count($chan) or cr_chk_flag($chan, CRF_BOTSTAY(), 1);
468 }
469 $get_bots_chans->finish();
470
471 $update_chanreg_bot->execute($botnick, $oldnick); $update_chanreg_bot->finish();
472
473 my $src = get_user_nick($user);
474 $delete_bot->execute($oldnick);
475 agent_quit($oldnick, "Deleted by \002$src\002.");
476 ircd::unsqline($oldnick);
477 notice($user, "Bot \002$oldnick\002 disconnected.");
478}
479
480### CHANNEL COMMANDS ###
481
482sub chan_dispatch($$$) {
483 my ($src, $cn, $msg) = @_;
484
485 my @args = split(/\s+/, $msg);
486 my $cmd = lc(shift @args);
487 $cmd =~ s/^\!//;
488
489 my $chan = { CHAN => $cn };
490 my $user = { NICK => $src, AGENT => agent($chan) };
491
492 my %cmdhash = (
08fdcdbd 493 'voice' => \&give_ops,
494 'devoice' => \&give_ops,
495 'hop' => \&give_ops,
496 'halfop' => \&give_ops,
497 'dehop' => \&give_ops,
498 'dehalfop' => \&give_ops,
499 'op' => \&give_ops,
500 'deop' => \&give_ops,
501 'protect' => \&give_ops,
7b261bb8 502 'admin' => \&give_ops,
08fdcdbd 503 'deprotect' => \&give_ops,
7b261bb8 504 'deadmin' => \&give_ops,
505
506 'up' => \&up,
507
508 'down' => \&down,
509 'molest' => \&down,
510
511 'invite' => \&invite,
512
513 'kick' => \&kick,
514 'k' => \&kick,
515
516 'kb' => \&kickban,
517 'kickb' => \&kickban,
518 'kban' => \&kickban,
519 'kickban' => \&kickban,
520 'bk' => \&kickban,
521 'bkick' => \&kickban,
522 'bank' => \&kickban,
523 'bankick' => \&kickban,
524
525 'kickmask' => \&kickmask,
526 'km' => \&kickmask,
527 'kmask' => \&kickmask,
528
529 'kickbanmask' => \&kickbanmask,
530 'kickbmask' => \&kickbanmask,
531 'kickbm' => \&kickbanmask,
532 'kbm' => \&kickbanmask,
533 'kbanm' => \&kickbanmask,
534 'kbanmask' => \&kickbanmask,
535 'kbmask' => \&kickbanmask,
536
537 'calc' => \&calc,
538
539 'seen' => \&seen,
540
541 #We really need something that is mostly obvious
542 # and won't be used by any other bots.
543 #TriviaBot I added !trivhelp
544 # I guess anope uses !commands
545 'help' => \&help,
546 'commands' => \&help,
547 'botcmds' => \&help,
548
08fdcdbd 549 'abbrevs' => \&help,
550 'abbreviations' => \&help,
551 'abbrev' => \&help,
552
7b261bb8 553 'users' => \&alist,
554 'alist' => \&alist,
555
556 'unban' => \&unban,
557
1aec5bc9 558 'banlist' => \&banlist,
559 'blist' => \&banlist,
560
7b261bb8 561 'ban' => \&ban,
562 'b' => \&ban,
563 'qban' => \&ban,
564 'nban' => \&ban,
565
566 'd' => \&dice,
567 'dice' => \&dice,
568
569 'mode' => \&mode,
570 'm' => \&mode,
571
572 'resync' => \&resync,
c5de8dcc 573
574 'topic' => \&topic,
575 't' => \&topic,
ed97ca33 576
577 'why' => \&why,
44b46f5b 578 'tempban' => \&tempban,
579 'tmpban' => \&tempban,
580 "tb" => \&tempban,
7b261bb8 581 );
582
583 sub give_ops {
696bcc83 584 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 585 chanserv::cs_setmodes($user, $cmd, $chan, @args);
586 }
587 sub up {
696bcc83 588 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 589 chanserv::cs_updown($user, $cmd, $chan->{CHAN}, @args);
590 }
591 sub down {
696bcc83 592 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 593 if(lc $cmd eq 'molest') {
594 chanserv::unset_modes($user, $chan);
595 } else {
596 chanserv::cs_updown($user, $cmd, $chan->{CHAN}, @args);
597 }
598 }
599
600 sub invite {
696bcc83 601 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 602 chanserv::cs_invite($user, $chan, @args) unless @args == 0;
603 }
604
605 sub kick {
696bcc83 606 my ($user, $chan, $cmd, undef, @args) = @_;
c1dee9a1 607 my $target = shift @args or return;
7b261bb8 608 chanserv::cs_kick($user, $chan, $target, 0, join(' ', @args));
609 }
44b46f5b 610 sub tempban {
611 my ($user, $chan, $cmd, undef, @args) = @_;
612 my $target = shift @args or return;
613 chanserv::cs_tempban($user, $chan, $target, join(' ', @args));
614 }
7b261bb8 615 sub kickban {
696bcc83 616 my ($user, $chan, $cmd, undef, @args) = @_;
c1dee9a1 617 my $target = shift @args or return;
7b261bb8 618 chanserv::cs_kick($user, $chan, $target, 1, join(' ', @args));
619 }
620
621 sub kickmask {
696bcc83 622 my ($user, $chan, $cmd, undef, @args) = @_;
c1dee9a1 623 my $target = shift @args or return;
7b261bb8 624 chanserv::cs_kickmask($user, $chan, $target, 0, join(' ', @args));
625 }
626 sub kickbanmask {
696bcc83 627 my ($user, $chan, $cmd, undef, @args) = @_;
c1dee9a1 628 my $target = shift @args or return;
7b261bb8 629 chanserv::cs_kickmask($user, $chan, $target, 1, join(' ', @args));
630 }
631
632 sub calc {
696bcc83 633 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 634 my $msg = join(' ', @args);
635 for ($msg) {
636 s/,/./g;
637 s/[^*.+0-9&|)(x\/^-]//g;
638 s/([*+\\.\/x-])\1*/$1/g;
639 s/\^/**/g;
640 s/(?<!0)x//g;
641 }
642
643 my $answer = $calc_safe->reval("($msg) || 0");
644 $answer = 'ERROR' unless defined $answer;
645
646 notice($user, ($@ ? "$msg = ERROR (${\ (split / at/, $@, 2)[0]})" : "$msg = $answer"));
647 }
648
649 sub seen {
696bcc83 650 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 651
652 if(@args >= 1) {
653 nickserv::ns_seen($user, @args);
654 } else {
655 notice($user, 'Syntax: SEEN <nick> [nick ...]');
656 }
657 }
658
659 sub help {
696bcc83 660 my ($user, $chan, $cmd, undef, @args) = @_;
08fdcdbd 661 if($cmd =~ /^abbrev(iation)?s?$/) {
662 sendhelp($user, 'chanbot', 'abbreviations');
663 } else {
664 sendhelp($user, 'chanbot');
665 }
7b261bb8 666 }
667
668 sub alist {
696bcc83 669 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 670 chanserv::cs_alist($user, $chan);
671 }
672
673 sub unban {
696bcc83 674 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 675 if(@args == 0) {
676 chanserv::cs_unban($user, $chan, get_user_nick($user));
677 }
678 elsif(@args >= 1) {
679 chanserv::cs_unban($user, $chan, @args);
680 }
681 }
682
683 sub ban {
696bcc83 684 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 685 $cmd =~ /^(q|n)?ban$/; my $type = $1;
686 if(@args >= 1) {
687 chanserv::cs_ban($user, $chan, $type, @args);
688 }
689 }
690
1aec5bc9 691 sub banlist {
696bcc83 692 my ($user, $chan, $cmd, undef, @args) = @_;
1aec5bc9 693 chanserv::cs_banlist($user, $chan);
694 }
695
7b261bb8 696 sub dice {
697 # FIXME: If dice is disabled, don't count towards flooding.
696bcc83 698 my ($user, $chan, $cmd, undef, @args) = @_;
699
674d6fee 700 if(chanserv::can_do($chan, 'DICE', $user)) {
7b261bb8 701 ircd::privmsg(agent($chan), $chan->{CHAN},
702 get_dice($args[0]));
703 }
704 }
705
706 sub mode {
696bcc83 707 my ($user, $chan, $cmd, undef, @args) = @_;
7b261bb8 708 if(@args >= 1) {
709 chanserv::cs_mode($user, $chan, shift @args, @args);
710 }
711 }
712
713 sub resync {
696bcc83 714 my ($user, $chan, $cmd) = @_;
7b261bb8 715 chanserv::cs_resync($user, $chan->{CHAN});
716 }
717
c5de8dcc 718 sub topic {
696bcc83 719 my ($user, $chan, $cmd, $msg) = @_;
c5de8dcc 720 if (@args >= 1) {
696bcc83 721 $msg =~ s/^!$cmd //;
5b9e7750 722 chanserv::cs_topic($user, $chan, $msg);
c5de8dcc 723 }
724 }
725
bf645f27 726 sub why {
ed97ca33 727 my ($user, $chan, $cmd, undef, @args) = @_;
728
729 if(@args >= 1) {
bf645f27 730 chanserv::cs_why($user, $chan, @args);
ed97ca33 731 } else {
732 notice($user, 'Syntax: WHY <nick> [nick ...]');
733 }
734 }
7b261bb8 735 if(defined($cmdhash{$cmd})) {
920a4abc 736 return if flood_check($user);
7b261bb8 737
696bcc83 738 &{$cmdhash{$cmd}}($user, $chan, $cmd, $msg, @args);
7b261bb8 739 }
740}
741
742sub bot_say($$$) {
743 my ($user, $chan, $botmsg) = @_;
744 my $cn = $chan->{CHAN};
745
674d6fee 746 if(chanserv::can_do($chan, 'BotSay', $user)) {
7b261bb8 747 ircd::notice(agent($chan), '%'.$cn, get_user_nick($user).' used BotSay')
748 if cr_chk_flag($chan, CRF_VERBOSE());
749 ircd::privmsg(agent($chan), $cn, $botmsg);
750 } else {
e00fcba1 751 # can_do will give the $err_deny for us.
752 #notice($user, $err_deny);
7b261bb8 753 }
754}
755
756### BOT COMMANDS ###
757
758sub bot_dispatch($$$) {
759 my ($src, $bot, $msg) = @_;
760
761 my ($cmd, $cn, $botmsg) = split(/ /, $msg, 3);
762
763 my $user = { NICK => $src, AGENT => $bot };
764 my $chan = { CHAN => $cn };
765
920a4abc 766 return if flood_check($user);
7b261bb8 767
768 if ($cmd =~ /^join$/i) {
769 if (adminserv::can_do($user, 'BOT')) {
770 agent_join($bot, $cn);
771 } else {
772 notice($user, $err_deny);
773 }
774 }
775 elsif ($cmd =~ /^part$/i) {
776 if (adminserv::can_do($user, 'BOT')) {
777 agent_part($bot, $cn, "$src requested part");
778 } else {
779 notice($user, $err_deny);
780 }
781 }
782 elsif ($cmd =~ /^say$/i) {
783 bot_say($user, $chan, $botmsg);
784 }
785 elsif ($cmd =~ /^act$/i) {
786 bot_say($user, $chan, "\001ACTION $botmsg\001");
787 }
788 elsif ($cmd =~ /^help$/i) {
789 #my @help; @help = ($cn) if $cn; push @help, split(/\s+/, $botmsg);
790 sendhelp($user, 'botpriv');
791 }
792}
793
794sub get_dice($) {
795 my ($count, $sides) = map int($_), ($_[0] ? split('d', $_[0]) : (1, 6));
796
797 if ($sides < 1 or $sides > 1000 or $count < 0 or $count > 100) {
798 return "Sorry, you can't have more than 100 dice, or 1000 sides, or less than 1 of either.";
799 }
800 $count = 1 if $count == 0;
801
802 my $sum = 0;
803
804 if($count == 1 or $count > 25) {
805 for(my $i = 1; $i <= $count; $i++) {
806 $sum += int(rand($sides)+1);
807 }
808
809 return "${count}d$sides: $sum";
810 }
811 else {
812 my @dice;
813
814 for(my $i = 1; $i <= $count; $i++) {
815 my $n = int(rand($sides)+1);
816 $sum += $n;
817 push @dice, $n;
818 }
819
820 return "${count}d$sides: $sum [" . join(' ', sort {$a <=> $b} @dice) . "]";
821 }
822}
823
824### IRC EVENTS ###
825
826sub chan_msg($$$) {
827 #We don't do chanmsg processing yet, like badwords.
828}
829
830sub register() {
831 $get_all_bots->execute();
832 while(my ($nick, $ident, $vhost, $gecos, $flags) = $get_all_bots->fetchrow_array) {
833 agent_connect($nick, $ident, $vhost, '+pqBSrz'.(($flags & F_DEAF())?'d':''), $gecos);
834 ircd::sqline($nick, $services::qlreason);
835 agent_join($nick, main_conf_diag);
836 ircd::setmode($main::rsnick, main_conf_diag, '+h', $nick);
837 }
838}
839
840sub eos() {
841 $get_botchans->execute();
842 while(my ($cn, $nick) = $get_botchans->fetchrow_array) {
843 my $chan = { CHAN => $cn };
844 if(chanserv::get_user_count($chan)) {
845 bot_join($chan, $nick);
846 }
847 elsif(cr_chk_flag($chan, CRF_BOTSTAY(), 1)) {
848 bot_join($chan, $nick);
849 my $modelock = chanserv::get_modelock($chan);
850 ircd::setmode(main_conf_local, $cn, $modelock) if $modelock;
851 }
852 }
853}
854
855### Database Functions ###
856
857sub set_flag($$) {
858 my ($bot, $flag) = @_;
859
860 $set_flag->execute($flag, $bot);
861}
862
863sub unset_flag($$) {
864 my ($bot, $flag) = @_;
865
866 $unset_flag->execute($flag, $bot);
867}
868
869sub bot_join($;$) {
870 my ($chan, $nick) = @_;
871
872 my $cn = $chan->{CHAN};
873
874 $nick = agent($chan) unless $nick;
875
876 unless(is_agent_in_chan($nick, $cn)) {
877 agent_join($nick, $cn);
878 ircd::setmode($nick, $cn, $botchmode, $nick.(ircd::PREFIXAQ_DISABLE() ? ' '.$nick : '') );
879 }
880}
881
882sub bot_part_if_needed($$$;$) {
883 my ($nick, $chan, $reason, $empty) = @_;
884 my $cn = $chan->{CHAN};
885 my $bot = get_chan_bot($chan);
886 $nick = agent($chan) unless $nick;
887
888 return if (lc $chanserv::enforcers{lc $cn} eq lc $nick);
889
890 if(is_agent_in_chan($nick, $cn)) {
891 if(lc $bot eq lc $nick) {
892 if(cr_chk_flag($chan, CRF_BOTSTAY(), 1) or ($empty != 1 or chanserv::get_user_count($chan))) {
893 return;
894 }
895 }
896
897 agent_part($nick, $cn, $reason);
898 }
899}
900
901sub get_chan_bot($) {
902 my ($chan) = @_;
903 my $cn = $chan->{CHAN};
904 $botserv::get_chan_bot->execute($cn);
905
906 my ($bot) = $botserv::get_chan_bot->fetchrow_array();
907 $botserv::get_chan_bot->finish();
908
909 return $bot;
910}
911
9121;