]> jfr.im git - irc/SurrealServices/srsv.git/blob - branches/0.4.3/modules/serviceslibs/botserv.pm
6041cfe46b41e0420ec34350b3a52f2aad8a6b63
[irc/SurrealServices/srsv.git] / branches / 0.4.3 / modules / serviceslibs / botserv.pm
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
16 package botserv;
17
18 use strict;
19 no strict 'refs';
20
21 use Safe;
22
23 use SrSv::Agent;
24 use SrSv::Process::Worker 'ima_worker'; #FIXME
25
26 use SrSv::Text::Format qw(columnar);
27 use SrSv::Errors;
28
29 use SrSv::Conf2Consts qw( main services );
30
31 use SrSv::User qw(get_user_nick get_user_id :flood);
32 use SrSv::User::Notice;
33 use SrSv::Help qw( sendhelp );
34
35 use SrSv::ChanReg::Flags;
36 use SrSv::NickReg::Flags qw(NRF_NOHIGHLIGHT nr_chk_flag_user);
37
38 use SrSv::MySQL '$dbh';
39
40 use constant {
41 F_PRIVATE => 1,
42 F_DEAF => 2
43 };
44
45 our $bsnick_default = 'BotServ';
46 our $bsnick = $bsnick_default;
47 our $botchmode;
48 if(!ircd::PREFIXAQ_DISABLE()) {
49 $botchmode = '+q';
50 } else {
51 $botchmode = '+qo';
52 }
53
54 *agent = \&chanserv::agent;
55
56 our $calc_safe = new Safe;
57
58 our (
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
69 sub 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
97 sub 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
122 sub bs_dispatch($$$) {
123 my ($src, $dst, $msg) = @_;
124 $msg =~ s/^\s+//;
125 my @args = split(/\s+/, $msg);
126 my $cmd = shift @args;
127
128 my $user = { NICK => $src, AGENT => $dst };
129
130 return if flood_check($user);
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 #
221 sub bs_assign($$$) {
222 my ($user, $chan, $bot) = @_;
223
224 chanserv::chk_registered($user, $chan) or return;
225
226 unless (chanserv::can_do($chan, 'BotAssign', $user)) {
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
266 sub 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
286 sub 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
324 sub 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
346 sub 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
383 sub 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
417 sub 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
482 sub 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 = (
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,
502 'admin' => \&give_ops,
503 'deprotect' => \&give_ops,
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
549 'abbrevs' => \&help,
550 'abbreviations' => \&help,
551 'abbrev' => \&help,
552
553 'users' => \&alist,
554 'alist' => \&alist,
555
556 'unban' => \&unban,
557
558 'banlist' => \&banlist,
559 'blist' => \&banlist,
560
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,
573
574 'topic' => \&topic,
575 't' => \&topic,
576
577 'why' => \&why,
578 'tempban' => \&tempban,
579 'tmpban' => \&tempban,
580 "tb" => \&tempban,
581 );
582
583 sub give_ops {
584 my ($user, $chan, $cmd, undef, @args) = @_;
585 chanserv::cs_setmodes($user, $cmd, $chan, @args);
586 }
587 sub up {
588 my ($user, $chan, $cmd, undef, @args) = @_;
589 chanserv::cs_updown($user, $cmd, $chan->{CHAN}, @args);
590 }
591 sub down {
592 my ($user, $chan, $cmd, undef, @args) = @_;
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 {
601 my ($user, $chan, $cmd, undef, @args) = @_;
602 chanserv::cs_invite($user, $chan, @args) unless @args == 0;
603 }
604
605 sub kick {
606 my ($user, $chan, $cmd, undef, @args) = @_;
607 my $target = shift @args or return;
608 chanserv::cs_kick($user, $chan, $target, 0, join(' ', @args));
609 }
610 sub tempban {
611 my ($user, $chan, $cmd, undef, @args) = @_;
612
613 my $cn = $chan->{CHAN};
614 use Data::Dumper;
615
616 unshift @args, $cn;
617 print ("ARGS " . Dumper (@args));
618 chanserv::cs_tempban($user, join(' ', @args));
619 }
620 sub kickban {
621 my ($user, $chan, $cmd, undef, @args) = @_;
622 my $target = shift @args or return;
623 chanserv::cs_kick($user, $chan, $target, 1, join(' ', @args));
624 }
625
626 sub kickmask {
627 my ($user, $chan, $cmd, undef, @args) = @_;
628 my $target = shift @args or return;
629 chanserv::cs_kickmask($user, $chan, $target, 0, join(' ', @args));
630 }
631 sub kickbanmask {
632 my ($user, $chan, $cmd, undef, @args) = @_;
633 my $target = shift @args or return;
634 chanserv::cs_kickmask($user, $chan, $target, 1, join(' ', @args));
635 }
636
637 sub calc {
638 my ($user, $chan, $cmd, undef, @args) = @_;
639 my $msg = join(' ', @args);
640 for ($msg) {
641 s/,/./g;
642 s/[^*.+0-9&|)(x\/^-]//g;
643 s/([*+\\.\/x-])\1*/$1/g;
644 s/\^/**/g;
645 s/(?<!0)x//g;
646 }
647
648 my $answer = $calc_safe->reval("($msg) || 0");
649 $answer = 'ERROR' unless defined $answer;
650
651 notice($user, ($@ ? "$msg = ERROR (${\ (split / at/, $@, 2)[0]})" : "$msg = $answer"));
652 }
653
654 sub seen {
655 my ($user, $chan, $cmd, undef, @args) = @_;
656
657 if(@args >= 1) {
658 nickserv::ns_seen($user, @args);
659 } else {
660 notice($user, 'Syntax: SEEN <nick> [nick ...]');
661 }
662 }
663
664 sub help {
665 my ($user, $chan, $cmd, undef, @args) = @_;
666 if($cmd =~ /^abbrev(iation)?s?$/) {
667 sendhelp($user, 'chanbot', 'abbreviations');
668 } else {
669 sendhelp($user, 'chanbot');
670 }
671 }
672
673 sub alist {
674 my ($user, $chan, $cmd, undef, @args) = @_;
675 chanserv::cs_alist($user, $chan);
676 }
677
678 sub unban {
679 my ($user, $chan, $cmd, undef, @args) = @_;
680 if(@args == 0) {
681 chanserv::cs_unban($user, $chan, get_user_nick($user));
682 }
683 elsif(@args >= 1) {
684 chanserv::cs_unban($user, $chan, @args);
685 }
686 }
687
688 sub ban {
689 my ($user, $chan, $cmd, undef, @args) = @_;
690 $cmd =~ /^(q|n)?ban$/; my $type = $1;
691 if(@args >= 1) {
692 chanserv::cs_ban($user, $chan, $type, @args);
693 }
694 }
695
696 sub banlist {
697 my ($user, $chan, $cmd, undef, @args) = @_;
698 chanserv::cs_banlist($user, $chan);
699 }
700
701 sub dice {
702 # FIXME: If dice is disabled, don't count towards flooding.
703 my ($user, $chan, $cmd, undef, @args) = @_;
704
705 if(chanserv::can_do($chan, 'DICE', $user)) {
706 ircd::privmsg(agent($chan), $chan->{CHAN},
707 get_dice($args[0]));
708 }
709 }
710
711 sub mode {
712 my ($user, $chan, $cmd, undef, @args) = @_;
713 if(@args >= 1) {
714 chanserv::cs_mode($user, $chan, shift @args, @args);
715 }
716 }
717
718 sub resync {
719 my ($user, $chan, $cmd) = @_;
720 chanserv::cs_resync($user, $chan->{CHAN});
721 }
722
723 sub topic {
724 my ($user, $chan, $cmd, $msg) = @_;
725 if (@args >= 1) {
726 $msg =~ s/^!$cmd //;
727 chanserv::cs_topic($user, $chan, $msg);
728 }
729 }
730
731 sub why {
732 my ($user, $chan, $cmd, undef, @args) = @_;
733
734 if(@args >= 1) {
735 chanserv::cs_why($user, $chan, @args);
736 } else {
737 notice($user, 'Syntax: WHY <nick> [nick ...]');
738 }
739 }
740 if(defined($cmdhash{$cmd})) {
741 return if flood_check($user);
742
743 &{$cmdhash{$cmd}}($user, $chan, $cmd, $msg, @args);
744 }
745 }
746
747 sub bot_say($$$) {
748 my ($user, $chan, $botmsg) = @_;
749 my $cn = $chan->{CHAN};
750
751 if(chanserv::can_do($chan, 'BotSay', $user)) {
752 ircd::notice(agent($chan), '%'.$cn, get_user_nick($user).' used BotSay')
753 if cr_chk_flag($chan, CRF_VERBOSE());
754 ircd::privmsg(agent($chan), $cn, $botmsg);
755 } else {
756 # can_do will give the $err_deny for us.
757 #notice($user, $err_deny);
758 }
759 }
760
761 ### BOT COMMANDS ###
762
763 sub bot_dispatch($$$) {
764 my ($src, $bot, $msg) = @_;
765
766 my ($cmd, $cn, $botmsg) = split(/ /, $msg, 3);
767
768 my $user = { NICK => $src, AGENT => $bot };
769 my $chan = { CHAN => $cn };
770
771 return if flood_check($user);
772
773 if ($cmd =~ /^join$/i) {
774 if (adminserv::can_do($user, 'BOT')) {
775 agent_join($bot, $cn);
776 } else {
777 notice($user, $err_deny);
778 }
779 }
780 elsif ($cmd =~ /^part$/i) {
781 if (adminserv::can_do($user, 'BOT')) {
782 agent_part($bot, $cn, "$src requested part");
783 } else {
784 notice($user, $err_deny);
785 }
786 }
787 elsif ($cmd =~ /^say$/i) {
788 bot_say($user, $chan, $botmsg);
789 }
790 elsif ($cmd =~ /^act$/i) {
791 bot_say($user, $chan, "\001ACTION $botmsg\001");
792 }
793 elsif ($cmd =~ /^help$/i) {
794 #my @help; @help = ($cn) if $cn; push @help, split(/\s+/, $botmsg);
795 sendhelp($user, 'botpriv');
796 }
797 }
798
799 sub get_dice($) {
800 my ($count, $sides) = map int($_), ($_[0] ? split('d', $_[0]) : (1, 6));
801
802 if ($sides < 1 or $sides > 1000 or $count < 0 or $count > 100) {
803 return "Sorry, you can't have more than 100 dice, or 1000 sides, or less than 1 of either.";
804 }
805 $count = 1 if $count == 0;
806
807 my $sum = 0;
808
809 if($count == 1 or $count > 25) {
810 for(my $i = 1; $i <= $count; $i++) {
811 $sum += int(rand($sides)+1);
812 }
813
814 return "${count}d$sides: $sum";
815 }
816 else {
817 my @dice;
818
819 for(my $i = 1; $i <= $count; $i++) {
820 my $n = int(rand($sides)+1);
821 $sum += $n;
822 push @dice, $n;
823 }
824
825 return "${count}d$sides: $sum [" . join(' ', sort {$a <=> $b} @dice) . "]";
826 }
827 }
828
829 ### IRC EVENTS ###
830
831 sub chan_msg($$$) {
832 #We don't do chanmsg processing yet, like badwords.
833 }
834
835 sub register() {
836 $get_all_bots->execute();
837 while(my ($nick, $ident, $vhost, $gecos, $flags) = $get_all_bots->fetchrow_array) {
838 agent_connect($nick, $ident, $vhost, '+pqBSrz'.(($flags & F_DEAF())?'d':''), $gecos);
839 ircd::sqline($nick, $services::qlreason);
840 agent_join($nick, main_conf_diag);
841 ircd::setmode($main::rsnick, main_conf_diag, '+h', $nick);
842 }
843 }
844
845 sub eos() {
846 $get_botchans->execute();
847 while(my ($cn, $nick) = $get_botchans->fetchrow_array) {
848 my $chan = { CHAN => $cn };
849 if(chanserv::get_user_count($chan)) {
850 bot_join($chan, $nick);
851 }
852 elsif(cr_chk_flag($chan, CRF_BOTSTAY(), 1)) {
853 bot_join($chan, $nick);
854 my $modelock = chanserv::get_modelock($chan);
855 ircd::setmode(main_conf_local, $cn, $modelock) if $modelock;
856 }
857 }
858 }
859
860 ### Database Functions ###
861
862 sub set_flag($$) {
863 my ($bot, $flag) = @_;
864
865 $set_flag->execute($flag, $bot);
866 }
867
868 sub unset_flag($$) {
869 my ($bot, $flag) = @_;
870
871 $unset_flag->execute($flag, $bot);
872 }
873
874 sub bot_join($;$) {
875 my ($chan, $nick) = @_;
876
877 my $cn = $chan->{CHAN};
878
879 $nick = agent($chan) unless $nick;
880
881 unless(is_agent_in_chan($nick, $cn)) {
882 agent_join($nick, $cn);
883 ircd::setmode($nick, $cn, $botchmode, $nick.(ircd::PREFIXAQ_DISABLE() ? ' '.$nick : '') );
884 }
885 }
886
887 sub bot_part_if_needed($$$;$) {
888 my ($nick, $chan, $reason, $empty) = @_;
889 my $cn = $chan->{CHAN};
890 my $bot = get_chan_bot($chan);
891 $nick = agent($chan) unless $nick;
892
893 return if (lc $chanserv::enforcers{lc $cn} eq lc $nick);
894
895 if(is_agent_in_chan($nick, $cn)) {
896 if(lc $bot eq lc $nick) {
897 if(cr_chk_flag($chan, CRF_BOTSTAY(), 1) or ($empty != 1 or chanserv::get_user_count($chan))) {
898 return;
899 }
900 }
901
902 agent_part($nick, $cn, $reason);
903 }
904 }
905
906 sub get_chan_bot($) {
907 my ($chan) = @_;
908 my $cn = $chan->{CHAN};
909 $botserv::get_chan_bot->execute($cn);
910
911 my ($bot) = $botserv::get_chan_bot->fetchrow_array();
912 $botserv::get_chan_bot->finish();
913
914 return $bot;
915 }
916
917 1;