]> jfr.im git - irc/SurrealServices/srsv.git/blob - branches/erry-devel/modules/serviceslibs/botserv.pm
faaf54c3032fa9987d4b965d0d59355998710d72
[irc/SurrealServices/srsv.git] / branches / erry-devel / 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 => $bsnick };
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 'users' => \&alist,
550 'alist' => \&alist,
551
552 'unban' => \&unban,
553
554 'banlist' => \&banlist,
555 'blist' => \&banlist,
556
557 'ban' => \&ban,
558 'b' => \&ban,
559 'qban' => \&ban,
560 'nban' => \&ban,
561
562 'd' => \&dice,
563 'dice' => \&dice,
564
565 'mode' => \&mode,
566 'm' => \&mode,
567
568 'resync' => \&resync,
569 );
570
571 sub give_ops {
572 my ($user, $cmd, $chan, @args) = @_;
573 chanserv::cs_setmodes($user, $cmd, $chan, @args);
574 }
575 sub up {
576 my ($user, $cmd, $chan, @args) = @_;
577 chanserv::cs_updown($user, $cmd, $chan->{CHAN}, @args);
578 }
579 sub down {
580 my ($user, $cmd, $chan, @args) = @_;
581 if(lc $cmd eq 'molest') {
582 chanserv::unset_modes($user, $chan);
583 } else {
584 chanserv::cs_updown($user, $cmd, $chan->{CHAN}, @args);
585 }
586 }
587
588 sub invite {
589 my ($user, $cmd, $chan, @args) = @_;
590 chanserv::cs_invite($user, $chan, @args) unless @args == 0;
591 }
592
593 sub kick {
594 my ($user, $cmd, $chan, @args) = @_;
595 my $target = shift @args;
596 chanserv::cs_kick($user, $chan, $target, 0, join(' ', @args));
597 }
598 sub kickban {
599 my ($user, $cmd, $chan, @args) = @_;
600 my $target = shift @args;
601 chanserv::cs_kick($user, $chan, $target, 1, join(' ', @args));
602 }
603
604 sub kickmask {
605 my ($user, $cmd, $chan, @args) = @_;
606 my $target = shift @args;
607 chanserv::cs_kickmask($user, $chan, $target, 0, join(' ', @args));
608 }
609 sub kickbanmask {
610 my ($user, $cmd, $chan, @args) = @_;
611 my $target = shift @args;
612 chanserv::cs_kickmask($user, $chan, $target, 1, join(' ', @args));
613 }
614
615 sub calc {
616 my ($user, $cmd, $chan, @args) = @_;
617 my $msg = join(' ', @args);
618 for ($msg) {
619 s/,/./g;
620 s/[^*.+0-9&|)(x\/^-]//g;
621 s/([*+\\.\/x-])\1*/$1/g;
622 s/\^/**/g;
623 s/(?<!0)x//g;
624 }
625
626 my $answer = $calc_safe->reval("($msg) || 0");
627 $answer = 'ERROR' unless defined $answer;
628
629 notice($user, ($@ ? "$msg = ERROR (${\ (split / at/, $@, 2)[0]})" : "$msg = $answer"));
630 }
631
632 sub seen {
633 my ($user, $cmd, $chan, @args) = @_;
634
635 if(@args >= 1) {
636 nickserv::ns_seen($user, @args);
637 } else {
638 notice($user, 'Syntax: SEEN <nick> [nick ...]');
639 }
640 }
641
642 sub help {
643 my ($user, $cmd, $chan, @args) = @_;
644 sendhelp($user, 'chanbot');
645 }
646
647 sub alist {
648 my ($user, $cmd, $chan, @args) = @_;
649 chanserv::cs_alist($user, $chan);
650 }
651
652 sub unban {
653 my ($user, $cmd, $chan, @args) = @_;
654 if(@args == 0) {
655 chanserv::cs_unban($user, $chan, get_user_nick($user));
656 }
657 elsif(@args >= 1) {
658 chanserv::cs_unban($user, $chan, @args);
659 }
660 }
661
662 sub ban {
663 my ($user, $cmd, $chan, @args) = @_;
664 $cmd =~ /^(q|n)?ban$/; my $type = $1;
665 if(@args >= 1) {
666 chanserv::cs_ban($user, $chan, $type, @args);
667 }
668 }
669
670 sub banlist {
671 my ($user, $cmd, $chan, @args) = @_;
672 chanserv::cs_banlist($user, $chan);
673 }
674
675 sub dice {
676 # FIXME: If dice is disabled, don't count towards flooding.
677 my ($user, $cmd, $chan, @args) = @_;
678
679 if(chanserv::can_do($chan, 'DICE', $user)) {
680 ircd::privmsg(agent($chan), $chan->{CHAN},
681 get_dice($args[0]));
682 }
683 }
684
685 sub mode {
686 my ($user, $cmd, $chan, @args) = @_;
687 if(@args >= 1) {
688 chanserv::cs_mode($user, $chan, shift @args, @args);
689 }
690 }
691
692 sub resync {
693 my ($user, $cmd, $chan) = @_;
694 chanserv::cs_resync($user, $chan->{CHAN});
695 }
696
697 if(defined($cmdhash{$cmd})) {
698 return if flood_check($user);
699
700 &{$cmdhash{$cmd}}($user, $cmd, $chan, @args);
701 }
702 }
703
704 sub bot_say($$$) {
705 my ($user, $chan, $botmsg) = @_;
706 my $cn = $chan->{CHAN};
707
708 if(chanserv::can_do($chan, 'BotSay', $user)) {
709 ircd::notice(agent($chan), '%'.$cn, get_user_nick($user).' used BotSay')
710 if cr_chk_flag($chan, CRF_VERBOSE());
711 ircd::privmsg(agent($chan), $cn, $botmsg);
712 } else {
713 notice($user, $err_deny);
714 }
715 }
716
717 ### BOT COMMANDS ###
718
719 sub bot_dispatch($$$) {
720 my ($src, $bot, $msg) = @_;
721
722 my ($cmd, $cn, $botmsg) = split(/ /, $msg, 3);
723
724 my $user = { NICK => $src, AGENT => $bot };
725 my $chan = { CHAN => $cn };
726
727 return if flood_check($user);
728
729 if ($cmd =~ /^join$/i) {
730 if (adminserv::can_do($user, 'BOT')) {
731 agent_join($bot, $cn);
732 } else {
733 notice($user, $err_deny);
734 }
735 }
736 elsif ($cmd =~ /^part$/i) {
737 if (adminserv::can_do($user, 'BOT')) {
738 agent_part($bot, $cn, "$src requested part");
739 } else {
740 notice($user, $err_deny);
741 }
742 }
743 elsif ($cmd =~ /^say$/i) {
744 bot_say($user, $chan, $botmsg);
745 }
746 elsif ($cmd =~ /^act$/i) {
747 bot_say($user, $chan, "\001ACTION $botmsg\001");
748 }
749 elsif ($cmd =~ /^help$/i) {
750 #my @help; @help = ($cn) if $cn; push @help, split(/\s+/, $botmsg);
751 sendhelp($user, 'botpriv');
752 }
753 }
754
755 sub get_dice($) {
756 my ($count, $sides) = map int($_), ($_[0] ? split('d', $_[0]) : (1, 6));
757
758 if ($sides < 1 or $sides > 1000 or $count < 0 or $count > 100) {
759 return "Sorry, you can't have more than 100 dice, or 1000 sides, or less than 1 of either.";
760 }
761 $count = 1 if $count == 0;
762
763 my $sum = 0;
764
765 if($count == 1 or $count > 25) {
766 for(my $i = 1; $i <= $count; $i++) {
767 $sum += int(rand($sides)+1);
768 }
769
770 return "${count}d$sides: $sum";
771 }
772 else {
773 my @dice;
774
775 for(my $i = 1; $i <= $count; $i++) {
776 my $n = int(rand($sides)+1);
777 $sum += $n;
778 push @dice, $n;
779 }
780
781 return "${count}d$sides: $sum [" . join(' ', sort {$a <=> $b} @dice) . "]";
782 }
783 }
784
785 ### IRC EVENTS ###
786
787 sub chan_msg($$$) {
788 #We don't do chanmsg processing yet, like badwords.
789 }
790
791 sub register() {
792 $get_all_bots->execute();
793 while(my ($nick, $ident, $vhost, $gecos, $flags) = $get_all_bots->fetchrow_array) {
794 agent_connect($nick, $ident, $vhost, '+pqBSrz'.(($flags & F_DEAF())?'d':''), $gecos);
795 ircd::sqline($nick, $services::qlreason);
796 agent_join($nick, main_conf_diag);
797 ircd::setmode($main::rsnick, main_conf_diag, '+h', $nick);
798 }
799 }
800
801 sub eos() {
802 $get_botchans->execute();
803 print "BS EOS???\n";
804 while(my ($cn, $nick) = $get_botchans->fetchrow_array) {
805 my $chan = { CHAN => $cn };
806 print "BS BOT $nick $cn\n";
807 if(chanserv::get_user_count($chan)) {
808 bot_join($chan, $nick);
809 }
810 elsif(cr_chk_flag($chan, CRF_BOTSTAY(), 1)) {
811 bot_join($chan, $nick);
812 my $modelock = chanserv::get_modelock($chan);
813 ircd::setmode(main_conf_local, $cn, $modelock) if $modelock;
814 }
815 }
816 }
817
818 ### Database Functions ###
819
820 sub set_flag($$) {
821 my ($bot, $flag) = @_;
822
823 $set_flag->execute($flag, $bot);
824 }
825
826 sub unset_flag($$) {
827 my ($bot, $flag) = @_;
828
829 $unset_flag->execute($flag, $bot);
830 }
831
832 sub bot_join($;$) {
833 my ($chan, $nick) = @_;
834
835 my $cn = $chan->{CHAN};
836
837 $nick = agent($chan) unless $nick;
838 print "Huuuuuuuuuuuuuuuh\n";
839 unless(is_agent_in_chan($nick, $cn)) {
840 print "Making $nick join $cn\n";
841 agent_join($nick, $cn);
842 ircd::setmode($nick, $cn, $botchmode, $nick.(ircd::PREFIXAQ_DISABLE() ? ' '.$nick : '') );
843 }
844 }
845
846 sub bot_part_if_needed($$$;$) {
847 my ($nick, $chan, $reason, $empty) = @_;
848 my $cn = $chan->{CHAN};
849 my $bot = get_chan_bot($chan);
850 $nick = agent($chan) unless $nick;
851 return if (lc $chanserv::enforcers{lc $cn} eq lc $nick);
852
853 if(is_agent_in_chan($nick, $cn)) {
854 if(lc $bot eq lc $nick) {
855 if(cr_chk_flag($chan, CRF_BOTSTAY(), 1) or ($empty != 1 or chanserv::get_user_count($chan))) {
856 return;
857 }
858 }
859
860 agent_part($nick, $cn, $reason);
861 }
862 }
863
864 sub get_chan_bot($) {
865 my ($chan) = @_;
866 my $cn = $chan->{CHAN};
867 $botserv::get_chan_bot->execute($cn);
868
869 my ($bot) = $botserv::get_chan_bot->fetchrow_array();
870 $botserv::get_chan_bot->finish();
871
872 return $bot;
873 }
874
875 1;