]>
Commit | Line | Data |
---|---|---|
aecfa1fd | 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; | |
026939ee | 22 | use SrSv::IRCd::State qw($ircline synced initial_synced %IRCd_capabilities); |
aecfa1fd | 23 | use SrSv::Agent; |
24 | use SrSv::Process::Worker 'ima_worker'; #FIXME | |
5e682044 | 25 | use SrSv::Insp::UUID; |
aecfa1fd | 26 | use SrSv::Text::Format qw(columnar); |
27 | use SrSv::Errors; | |
5e682044 | 28 | use Data::Dumper; |
aecfa1fd | 29 | |
30 | use SrSv::Conf2Consts qw( main services ); | |
31 | ||
32 | use SrSv::User qw(get_user_nick get_user_id :flood); | |
33 | use SrSv::User::Notice; | |
34 | use SrSv::Help qw( sendhelp ); | |
aecfa1fd | 35 | use SrSv::ChanReg::Flags; |
36 | use SrSv::NickReg::Flags qw(NRF_NOHIGHLIGHT nr_chk_flag_user); | |
026939ee | 37 | use Data::Dumper; |
aecfa1fd | 38 | use SrSv::MySQL '$dbh'; |
39 | ||
40 | use constant { | |
41 | F_PRIVATE => 1, | |
42 | F_DEAF => 2 | |
43 | }; | |
44 | ||
7d4055c0 | 45 | our $bsnick_default = 'BotServ1'; |
aecfa1fd | 46 | our $bsnick = $bsnick_default; |
47 | our $botchmode; | |
7b3a5814 | 48 | our $bsuser = { NICK => $bsnick, ID => ircd::getAgentUuid($bsnick) }; |
88eba747 | 49 | |
aecfa1fd | 50 | *agent = \&chanserv::agent; |
51 | ||
52 | our $calc_safe = new Safe; | |
53 | ||
54 | our ( | |
55 | $get_all_bots, $get_botchans, $get_botstay_chans, $get_chan_bot, $get_bots_chans, $get_bot_info, | |
56 | ||
57 | $create_bot, $delete_bot, $delete_bot_allchans, $assign_bot, $unassign_bot, | |
58 | $change_bot, $update_chanreg_bot, | |
59 | ||
60 | $is_bot, $has_bot, | |
61 | ||
62 | $set_flag, $unset_flag, $get_flags | |
63 | ); | |
64 | ||
65 | sub init() { | |
1eb006d9 | 66 | $bsuser = { NICK => $bsnick, ID => ircd::getAgentUuid($bsnick) }; |
aecfa1fd | 67 | $get_all_bots = $dbh->prepare("SELECT nick, ident, vhost, gecos, flags FROM bot"); |
68 | $get_botchans = $dbh->prepare("SELECT chan, COALESCE(bot, '$chanserv::csnick') FROM chanreg WHERE bot != '' OR (flags & ". CRF_BOTSTAY() . ")"); | |
69 | $get_botstay_chans = $dbh->prepare("SELECT chan, COALESCE(bot, '$chanserv::csnick') FROM chanreg WHERE (flags & ". | |
70 | CRF_BOTSTAY() . ")"); | |
71 | $get_chan_bot = $dbh->prepare("SELECT bot FROM chanreg WHERE chan=?"); | |
72 | $get_bots_chans = $dbh->prepare("SELECT chan FROM chanreg WHERE bot=?"); | |
73 | $get_bot_info = $dbh->prepare("SELECT nick, ident, vhost, gecos, flags FROM bot WHERE nick=?"); | |
74 | ||
75 | $create_bot = $dbh->prepare("INSERT INTO bot SET nick=?, ident=?, vhost=?, gecos=?"); | |
76 | $delete_bot = $dbh->prepare("DELETE FROM bot WHERE nick=?"); | |
77 | $delete_bot_allchans = $dbh->prepare("UPDATE chanreg SET bot='' WHERE bot=?"); | |
78 | $change_bot = $dbh->prepare("UPDATE bot SET nick=?, ident=?, vhost=?, gecos=? WHERE nick=?"); | |
79 | $update_chanreg_bot = $dbh->prepare("UPDATE chanreg SET bot=? WHERE bot=?"); | |
80 | ||
81 | $assign_bot = $dbh->prepare("UPDATE chanreg, bot SET chanreg.bot=bot.nick WHERE bot.nick=? AND chan=?"); | |
82 | $unassign_bot = $dbh->prepare("UPDATE chanreg SET chanreg.bot='' WHERE chan=?"); | |
83 | ||
84 | $is_bot = $dbh->prepare("SELECT 1 FROM bot WHERE nick=?"); | |
85 | $has_bot = $dbh->prepare("SELECT 1 FROM chanreg WHERE chan=? AND bot != ''"); | |
86 | ||
87 | $set_flag = $dbh->prepare("UPDATE bot SET flags=(flags | (?)) WHERE nick=?"); | |
88 | $unset_flag = $dbh->prepare("UPDATE bot SET flags=(flags & ~(?)) WHERE nick=?"); | |
89 | $get_flags = $dbh->prepare("SELECT flags FROM bot WHERE bot.nick=?"); | |
90 | ||
91 | register() unless ima_worker; #FIXME | |
92 | }; | |
93 | ||
94 | sub dispatch($$$) { | |
7b3a5814 | 95 | $bsuser = { NICK => $bsnick, ID => ircd::getAgentUuid($bsnick) }; |
5e682044 | 96 | my ($user, $dstUser, $msg) = @_; |
7b3a5814 | 97 | $user -> {AGENT} = $bsuser; |
5e682044 | 98 | my $src = $user->{NICK}; |
99 | my $dst = $dstUser->{NICK}; | |
100 | if(lc $dstUser->{NICK} eq lc $bsnick or lc $dstUser->{NICK} eq lc $bsnick_default ) { | |
101 | bs_dispatch($user, $dstUser, $msg); | |
aecfa1fd | 102 | } |
103 | elsif($dst =~ /^#/) { | |
104 | if($msg =~ /^\!/) { | |
105 | $has_bot->execute($dst); | |
106 | return unless($has_bot->fetchrow_array); | |
92c29160 | 107 | chan_dispatch($user, $dst, $msg); |
aecfa1fd | 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($$$) { | |
5e682044 | 123 | my ($user, $dst, $msg) = @_; |
aecfa1fd | 124 | $msg =~ s/^\s+//; |
125 | my @args = split(/\s+/, $msg); | |
126 | my $cmd = shift @args; | |
127 | ||
5e682044 | 128 | $user->{AGENT} = $dst; |
129 | my $src = $user->{NICK}; | |
aecfa1fd | 130 | |
131 | return if flood_check($user); | |
132 | ||
133 | if($cmd =~ /^assign$/i) { | |
134 | if (@args == 2) { | |
135 | bs_assign($user, {CHAN => $args[0]}, $args[1]); | |
136 | } else { | |
137 | notice($user, 'Syntax: ASSIGN <#channel> <bot>'); | |
138 | } | |
139 | } | |
140 | elsif ($cmd =~ /^unassign$/i) { | |
141 | if (@args == 1) { | |
142 | bs_assign($user, {CHAN => $args[0]}, ''); | |
143 | } else { | |
144 | notice($user, 'Syntax: UNASSIGN <#channel>'); | |
145 | } | |
146 | } | |
147 | elsif ($cmd =~ /^list$/i) { | |
148 | if(@args == 0) { | |
149 | bs_list($user); | |
150 | } else { | |
151 | notice($user, 'Syntax: LIST'); | |
152 | } | |
153 | } | |
154 | elsif ($cmd =~ /^add$/i) { | |
155 | if (@args >= 4) { | |
156 | @args = split(/\s+/, $msg, 5); | |
157 | bs_add($user, $args[1], $args[2], $args[3], $args[4]); | |
158 | } else { | |
159 | notice($user, 'Syntax: ADD <nick> <ident> <vhost> <realname>'); | |
160 | } | |
161 | } | |
162 | elsif ($cmd =~ /^change$/i) { | |
163 | if (@args >= 4) { | |
164 | @args = split(/\s+/, $msg, 6); | |
165 | bs_change($user, $args[1], $args[2], $args[3], $args[4], $args[5]); | |
166 | } else { | |
167 | notice($user, 'Syntax: ADD <oldnick> <nick> <ident> <vhost> <realname>'); | |
168 | } | |
169 | } | |
170 | elsif ($cmd =~ /^del(ete)?$/i) { | |
171 | if (@args == 1) { | |
172 | bs_del($user, $args[0]); | |
173 | } else { | |
174 | notice($user, 'Syntax: DEL <botnick>'); | |
175 | } | |
176 | } | |
177 | elsif($cmd =~ /^set$/i) { | |
178 | if(@args == 3) { | |
179 | bs_set($user, $args[0], $args[1], $args[2]); | |
180 | } else { | |
181 | notice($user, 'Syntax: SET <botnick> <option> <value>'); | |
182 | } | |
183 | } | |
184 | elsif($cmd =~ /^seen$/i) { | |
185 | if(@args >= 1) { | |
186 | nickserv::ns_seen($user, @args); | |
187 | } else { | |
188 | notice($user, 'Syntax: SEEN <nick> [nick ...]'); | |
189 | } | |
190 | } | |
191 | ||
192 | elsif($cmd =~ /^(say|act)$/i) { | |
193 | if(@args > 1) { | |
194 | my @args = split(/\s+/, $msg, 3); | |
195 | my $botmsg = $args[2]; | |
196 | $botmsg = "\001ACTION $botmsg\001" if(lc $cmd eq 'act'); | |
197 | bot_say($user, {CHAN => $args[1]}, $botmsg); | |
198 | } else { | |
199 | notice($user, 'Syntax: '.uc($cmd).' <#chan> <message>'); | |
200 | } | |
201 | } | |
202 | elsif($cmd =~ /^info$/i) { | |
203 | if(@args == 1) { | |
204 | bs_info($user, $args[0]); | |
205 | } else { | |
206 | notice($user, 'Syntax: INFO <botnick>'); | |
207 | } | |
208 | } | |
209 | elsif($cmd =~ /^help$/i) { | |
210 | sendhelp($user, 'botserv', @args); | |
211 | } | |
212 | elsif($cmd =~ /^d(ice)?$/i) { | |
213 | notice($user, get_dice($args[0])); | |
214 | } | |
215 | else { | |
216 | notice($user, "Unrecognized command. For help, type: \002/bs help\002"); | |
217 | } | |
218 | } | |
219 | ||
220 | # For unassign, set $bot to '' | |
221 | # | |
222 | sub bs_assign($$$) { | |
223 | my ($user, $chan, $bot) = @_; | |
224 | ||
225 | chanserv::chk_registered($user, $chan) or return; | |
226 | ||
227 | unless (chanserv::can_do($chan, 'BotAssign', $user)) { | |
228 | notice($user, $err_deny); | |
229 | return; | |
230 | } | |
231 | ||
232 | if ($bot) { | |
233 | $is_bot->execute($bot); | |
234 | unless($is_bot->fetchrow_array) { | |
235 | notice($user, "\002$bot\002 is not a bot."); | |
236 | return; | |
237 | } | |
238 | } | |
239 | ||
240 | $get_flags->execute($bot); | |
241 | my ($botflags) = $get_flags->fetchrow_array; | |
242 | if (($botflags & F_PRIVATE) && !adminserv::can_do($user, 'BOT')) { | |
243 | notice($user, $err_deny); | |
244 | return; | |
245 | } | |
246 | ||
247 | ||
248 | my $cn = $chan->{CHAN}; | |
249 | my $src = get_user_nick($user); | |
250 | my $oldbot; | |
251 | if ($oldbot = get_chan_bot($chan)) { | |
252 | agent_part($oldbot, $cn, "Unassigned by \002$src\002."); | |
253 | } | |
254 | ||
aecfa1fd | 255 | if($bot) { |
256 | $assign_bot->execute($bot, $cn); | |
1eb006d9 | 257 | my $botUser = { NICK=>$bot, ID=>ircd::getAgentUuid ($bot) }; |
5e682044 | 258 | bot_join($chan, $botUser); |
aecfa1fd | 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); | |
5e682044 | 319 | my $bot = { NICK => $botnick}; |
320 | get_user_id ($bot); | |
321 | my $rsuser = { NICK => $main::rsnick }; | |
322 | get_user_id ($rsuser); | |
7b3a5814 | 323 | $rsuser->{ID} = ($rsuser->{ID}); |
5e682044 | 324 | ircd::setmode($rsuser, main_conf_diag, '+h', $bot); |
aecfa1fd | 325 | notice($user, "Bot $botnick connected."); |
326 | } | |
327 | ||
328 | sub bs_del($$) { | |
329 | my ($user, $botnick) = @_; | |
330 | ||
331 | unless (adminserv::can_do($user, 'BOT')) { | |
332 | notice($user, $err_deny); | |
333 | return; | |
334 | } | |
335 | $is_bot->execute($botnick); | |
336 | if (!$is_bot->fetchrow_array) { | |
337 | notice($user, "\002$botnick\002 is not a bot."); | |
338 | return; | |
339 | } | |
340 | ||
341 | my $src = get_user_nick($user); | |
342 | $delete_bot->execute($botnick); | |
343 | agent_quit($botnick, "Deleted by \002$src\002."); | |
344 | ircd::unsqline($botnick); | |
345 | ||
346 | $delete_bot_allchans->execute($botnick); | |
347 | notice($user, "Bot \002$botnick\002 disconnected."); | |
348 | } | |
349 | ||
350 | sub bs_set($$$$) { | |
351 | my ($user, $botnick, $set, $parm) = @_; | |
352 | ||
353 | unless (adminserv::can_do($user, 'BOT')) { | |
354 | notice($user, $err_deny); | |
355 | return; | |
356 | } | |
357 | if($set =~ /^private$/i) { | |
358 | if ($parm =~ /^(on|true)$/i) { | |
359 | set_flag($botnick, F_PRIVATE()); | |
360 | notice($user, "\002$botnick\002 is now private."); | |
361 | } | |
362 | elsif ($parm =~ /^(off|false)$/i) { | |
363 | unset_flag($botnick, F_PRIVATE()); | |
364 | notice($user, "\002$botnick\002 is now public."); | |
365 | } | |
366 | else { | |
367 | notice($user, 'Syntax: SET <botnick> PRIVATE <ON|OFF>'); | |
368 | } | |
369 | } | |
370 | if($set =~ /^deaf$/i) { | |
371 | if ($parm =~ /^(on|true)$/i) { | |
372 | set_flag($botnick, F_DEAF()); | |
373 | setagent_umode($botnick, '+d'); | |
374 | notice($user, "\002$botnick\002 is now deaf."); | |
375 | } | |
376 | elsif ($parm =~ /^(off|false)$/i) { | |
377 | unset_flag($botnick, F_DEAF()); | |
378 | setagent_umode($botnick, '-d'); | |
379 | notice($user, "\002$botnick\002 is now undeaf."); | |
380 | } | |
381 | else { | |
382 | notice($user, 'Syntax: SET <botnick> DEAF <ON|OFF>'); | |
383 | } | |
384 | } | |
385 | } | |
386 | ||
387 | sub bs_info($$) { | |
388 | my ($user, $botnick) = @_; | |
389 | ||
390 | unless (adminserv::can_do($user, 'HELP')) { | |
391 | notice($user, $err_deny); | |
392 | return; | |
393 | } | |
394 | $is_bot->execute($botnick); | |
395 | unless($is_bot->fetchrow_array) { | |
396 | notice($user, "\002$botnick\002 is not a bot."); | |
397 | return; | |
398 | } | |
399 | ||
400 | $get_bot_info->execute($botnick); | |
401 | my ($nick, $ident, $vhost, $gecos, $flags) = $get_bot_info->fetchrow_array; | |
402 | $get_bot_info->finish(); | |
403 | $get_bots_chans->execute($botnick); | |
404 | my @chans = (); | |
405 | while (my $chan = $get_bots_chans->fetchrow_array) { | |
406 | push @chans, $chan; | |
407 | } | |
408 | $get_bots_chans->finish(); | |
409 | ||
410 | notice($user, columnar({TITLE => "Information for bot \002$nick\002:", | |
411 | NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, | |
412 | ['Mask:', "$ident\@$vhost"], ['Realname:', $gecos], | |
413 | ['Flags:', (($flags & F_PRIVATE())?'Private ':'').(($flags & F_DEAF())?'Deaf ':'')], | |
414 | {COLLAPSE => [ | |
415 | 'Assigned to '. @chans.' channel(s):', | |
416 | ' ' . join(' ', @chans) | |
417 | ]} | |
418 | )); | |
419 | } | |
420 | ||
421 | sub bs_change($$$$$$) { | |
422 | my ($user, $oldnick, $botnick, $botident, $bothost, $botgecos) = @_; | |
423 | ||
424 | if (lc $oldnick eq lc $botnick) { | |
425 | notice($user, "Error: $oldnick is the same (case-insensitive) as $botnick", | |
426 | "At this time, you cannot change only the ident, host, gecos, or nick-case of a bot."); | |
427 | return; | |
428 | } | |
429 | ||
430 | unless (adminserv::can_do($user, 'BOT')) { | |
431 | notice($user, $err_deny); | |
432 | return; | |
433 | } | |
434 | ||
435 | if (my $ret = is_invalid_agentname($botnick, $botident, $bothost)) { | |
436 | notice($user, $ret); | |
437 | return; | |
438 | } | |
439 | ||
440 | if(nickserv::is_registered($botnick)) { | |
441 | notice($user, "The nick \002$botnick\002 is already registered."); | |
442 | return; | |
443 | } | |
444 | ||
445 | if(nickserv::is_online($botnick)) { | |
446 | notice($user, "The nick \002$botnick\002 is currently in use."); | |
447 | return; | |
448 | } | |
449 | ||
450 | $is_bot->execute($botnick); | |
451 | if($is_bot->fetchrow_array) { | |
452 | notice($user, "\002$botnick\002 already exists."); | |
453 | return; | |
454 | } | |
455 | ||
456 | #Create bot first, join it to its chans | |
457 | # then finally delete the old bot | |
458 | # This is to prevent races. | |
459 | $create_bot->execute($botnick, $botident, $bothost, $botgecos); | |
460 | ircd::sqline($botnick, $services::qlreason); | |
461 | agent_connect($botnick, $botident, $bothost, '+pqBSrz', $botgecos); | |
462 | agent_join($botnick, main_conf_diag); | |
5e682044 | 463 | my $rsnick = $main::rsnick; |
464 | my $rsuser = { NICK => $main::rsnick }; | |
465 | get_user_id ($rsuser); | |
7b3a5814 | 466 | $rsuser->{ID} = ($rsuser->{ID}); |
5e682044 | 467 | my $bot = { NICK => $botnick }; |
468 | get_user_id ($bot); | |
469 | ircd::setmode($rsuser, main_conf_diag, '+h', $bot); | |
aecfa1fd | 470 | |
471 | notice($user, "Bot $botnick connected."); | |
472 | ||
473 | $get_bots_chans->execute($oldnick); | |
474 | while(my ($cn) = $get_bots_chans->fetchrow_array()) { | |
475 | my $chan = { CHAN => $cn }; | |
7b3a5814 | 476 | my $botUser = { NICK=>$botnick, ID=>ircd::getAgentUuid($botnick) }; |
5e682044 | 477 | bot_join($chan, $botUser) |
aecfa1fd | 478 | if chanserv::get_user_count($chan) or cr_chk_flag($chan, CRF_BOTSTAY(), 1); |
479 | } | |
480 | $get_bots_chans->finish(); | |
481 | ||
482 | $update_chanreg_bot->execute($botnick, $oldnick); $update_chanreg_bot->finish(); | |
483 | ||
484 | my $src = get_user_nick($user); | |
485 | $delete_bot->execute($oldnick); | |
486 | agent_quit($oldnick, "Deleted by \002$src\002."); | |
487 | ircd::unsqline($oldnick); | |
488 | notice($user, "Bot \002$oldnick\002 disconnected."); | |
489 | } | |
490 | ||
491 | ### CHANNEL COMMANDS ### | |
492 | ||
493 | sub chan_dispatch($$$) { | |
5e682044 | 494 | my ($user, $cn, $msg) = @_; |
495 | my $src = $user->{NICK}; | |
aecfa1fd | 496 | my @args = split(/\s+/, $msg); |
497 | my $cmd = lc(shift @args); | |
498 | $cmd =~ s/^\!//; | |
92c29160 | 499 | my $chan = { CHAN => $cn }; |
500 | $user->{AGENT} = agent($chan); | |
aecfa1fd | 501 | my $chan = { CHAN => $cn }; |
aecfa1fd | 502 | |
503 | my %cmdhash = ( | |
504 | 'voice' => \&give_ops, | |
505 | 'devoice' => \&give_ops, | |
506 | 'hop' => \&give_ops, | |
507 | 'halfop' => \&give_ops, | |
508 | 'dehop' => \&give_ops, | |
509 | 'dehalfop' => \&give_ops, | |
510 | 'op' => \&give_ops, | |
511 | 'deop' => \&give_ops, | |
512 | 'protect' => \&give_ops, | |
513 | 'admin' => \&give_ops, | |
514 | 'deprotect' => \&give_ops, | |
515 | 'deadmin' => \&give_ops, | |
516 | ||
517 | 'up' => \&up, | |
518 | ||
519 | 'down' => \&down, | |
520 | 'molest' => \&down, | |
521 | ||
522 | 'invite' => \&invite, | |
523 | ||
524 | 'kick' => \&kick, | |
525 | 'k' => \&kick, | |
526 | ||
527 | 'kb' => \&kickban, | |
528 | 'kickb' => \&kickban, | |
529 | 'kban' => \&kickban, | |
530 | 'kickban' => \&kickban, | |
531 | 'bk' => \&kickban, | |
532 | 'bkick' => \&kickban, | |
533 | 'bank' => \&kickban, | |
534 | 'bankick' => \&kickban, | |
535 | ||
536 | 'kickmask' => \&kickmask, | |
537 | 'km' => \&kickmask, | |
538 | 'kmask' => \&kickmask, | |
539 | ||
540 | 'kickbanmask' => \&kickbanmask, | |
541 | 'kickbmask' => \&kickbanmask, | |
542 | 'kickbm' => \&kickbanmask, | |
543 | 'kbm' => \&kickbanmask, | |
544 | 'kbanm' => \&kickbanmask, | |
545 | 'kbanmask' => \&kickbanmask, | |
546 | 'kbmask' => \&kickbanmask, | |
547 | ||
548 | 'calc' => \&calc, | |
549 | ||
550 | 'seen' => \&seen, | |
551 | ||
552 | #We really need something that is mostly obvious | |
553 | # and won't be used by any other bots. | |
554 | #TriviaBot I added !trivhelp | |
555 | # I guess anope uses !commands | |
556 | 'help' => \&help, | |
557 | 'commands' => \&help, | |
558 | 'botcmds' => \&help, | |
559 | ||
560 | 'abbrevs' => \&help, | |
561 | 'abbreviations' => \&help, | |
562 | 'abbrev' => \&help, | |
563 | ||
564 | 'users' => \&alist, | |
565 | 'alist' => \&alist, | |
566 | ||
567 | 'unban' => \&unban, | |
568 | ||
569 | 'banlist' => \&banlist, | |
570 | 'blist' => \&banlist, | |
571 | ||
572 | 'ban' => \&ban, | |
573 | 'b' => \&ban, | |
574 | 'qban' => \&ban, | |
575 | 'nban' => \&ban, | |
576 | ||
577 | 'd' => \&dice, | |
578 | 'dice' => \&dice, | |
579 | ||
580 | 'mode' => \&mode, | |
581 | 'm' => \&mode, | |
582 | ||
583 | 'resync' => \&resync, | |
584 | ||
585 | 'topic' => \&topic, | |
586 | 't' => \&topic, | |
587 | ||
588 | 'why' => \&why, | |
589 | ); | |
590 | ||
591 | sub give_ops { | |
592 | my ($user, $chan, $cmd, undef, @args) = @_; | |
5e682044 | 593 | |
aecfa1fd | 594 | chanserv::cs_setmodes($user, $cmd, $chan, @args); |
595 | } | |
596 | sub up { | |
597 | my ($user, $chan, $cmd, undef, @args) = @_; | |
598 | chanserv::cs_updown($user, $cmd, $chan->{CHAN}, @args); | |
599 | } | |
600 | sub down { | |
601 | my ($user, $chan, $cmd, undef, @args) = @_; | |
602 | if(lc $cmd eq 'molest') { | |
603 | chanserv::unset_modes($user, $chan); | |
604 | } else { | |
605 | chanserv::cs_updown($user, $cmd, $chan->{CHAN}, @args); | |
606 | } | |
607 | } | |
608 | ||
609 | sub invite { | |
610 | my ($user, $chan, $cmd, undef, @args) = @_; | |
611 | chanserv::cs_invite($user, $chan, @args) unless @args == 0; | |
612 | } | |
613 | ||
614 | sub kick { | |
615 | my ($user, $chan, $cmd, undef, @args) = @_; | |
616 | my $target = shift @args or return; | |
617 | chanserv::cs_kick($user, $chan, $target, 0, join(' ', @args)); | |
618 | } | |
619 | sub kickban { | |
620 | my ($user, $chan, $cmd, undef, @args) = @_; | |
621 | my $target = shift @args or return; | |
622 | chanserv::cs_kick($user, $chan, $target, 1, join(' ', @args)); | |
623 | } | |
624 | ||
625 | sub kickmask { | |
626 | my ($user, $chan, $cmd, undef, @args) = @_; | |
627 | my $target = shift @args or return; | |
628 | chanserv::cs_kickmask($user, $chan, $target, 0, join(' ', @args)); | |
629 | } | |
630 | sub kickbanmask { | |
631 | my ($user, $chan, $cmd, undef, @args) = @_; | |
632 | my $target = shift @args or return; | |
633 | chanserv::cs_kickmask($user, $chan, $target, 1, join(' ', @args)); | |
634 | } | |
635 | ||
636 | sub calc { | |
637 | my ($user, $chan, $cmd, undef, @args) = @_; | |
638 | my $msg = join(' ', @args); | |
639 | for ($msg) { | |
640 | s/,/./g; | |
641 | s/[^*.+0-9&|)(x\/^-]//g; | |
642 | s/([*+\\.\/x-])\1*/$1/g; | |
643 | s/\^/**/g; | |
644 | s/(?<!0)x//g; | |
645 | } | |
646 | ||
647 | my $answer = $calc_safe->reval("($msg) || 0"); | |
648 | $answer = 'ERROR' unless defined $answer; | |
649 | ||
650 | notice($user, ($@ ? "$msg = ERROR (${\ (split / at/, $@, 2)[0]})" : "$msg = $answer")); | |
651 | } | |
652 | ||
653 | sub seen { | |
654 | my ($user, $chan, $cmd, undef, @args) = @_; | |
655 | ||
656 | if(@args >= 1) { | |
657 | nickserv::ns_seen($user, @args); | |
658 | } else { | |
659 | notice($user, 'Syntax: SEEN <nick> [nick ...]'); | |
660 | } | |
661 | } | |
662 | ||
663 | sub help { | |
664 | my ($user, $chan, $cmd, undef, @args) = @_; | |
665 | if($cmd =~ /^abbrev(iation)?s?$/) { | |
666 | sendhelp($user, 'chanbot', 'abbreviations'); | |
667 | } else { | |
668 | sendhelp($user, 'chanbot'); | |
669 | } | |
670 | } | |
671 | ||
672 | sub alist { | |
673 | my ($user, $chan, $cmd, undef, @args) = @_; | |
674 | chanserv::cs_alist($user, $chan); | |
675 | } | |
676 | ||
677 | sub unban { | |
678 | my ($user, $chan, $cmd, undef, @args) = @_; | |
679 | if(@args == 0) { | |
680 | chanserv::cs_unban($user, $chan, get_user_nick($user)); | |
681 | } | |
682 | elsif(@args >= 1) { | |
683 | chanserv::cs_unban($user, $chan, @args); | |
684 | } | |
685 | } | |
686 | ||
687 | sub ban { | |
688 | my ($user, $chan, $cmd, undef, @args) = @_; | |
689 | $cmd =~ /^(q|n)?ban$/; my $type = $1; | |
690 | if(@args >= 1) { | |
691 | chanserv::cs_ban($user, $chan, $type, @args); | |
692 | } | |
693 | } | |
694 | ||
695 | sub banlist { | |
696 | my ($user, $chan, $cmd, undef, @args) = @_; | |
697 | chanserv::cs_banlist($user, $chan); | |
698 | } | |
699 | ||
700 | sub dice { | |
701 | # FIXME: If dice is disabled, don't count towards flooding. | |
702 | my ($user, $chan, $cmd, undef, @args) = @_; | |
703 | ||
704 | if(chanserv::can_do($chan, 'DICE', $user)) { | |
705 | ircd::privmsg(agent($chan), $chan->{CHAN}, | |
706 | get_dice($args[0])); | |
707 | } | |
708 | } | |
709 | ||
710 | sub mode { | |
711 | my ($user, $chan, $cmd, undef, @args) = @_; | |
712 | if(@args >= 1) { | |
713 | chanserv::cs_mode($user, $chan, shift @args, @args); | |
714 | } | |
715 | } | |
716 | ||
717 | sub resync { | |
718 | my ($user, $chan, $cmd) = @_; | |
719 | chanserv::cs_resync($user, $chan->{CHAN}); | |
720 | } | |
721 | ||
722 | sub topic { | |
723 | my ($user, $chan, $cmd, $msg) = @_; | |
724 | if (@args >= 1) { | |
725 | $msg =~ s/^!$cmd //; | |
726 | chanserv::cs_topic($user, $chan, $msg); | |
727 | } | |
728 | } | |
729 | ||
730 | sub why { | |
731 | my ($user, $chan, $cmd, undef, @args) = @_; | |
732 | ||
733 | if(@args >= 1) { | |
734 | chanserv::cs_why($user, $chan, @args); | |
735 | } else { | |
736 | notice($user, 'Syntax: WHY <nick> [nick ...]'); | |
737 | } | |
738 | } | |
739 | if(defined($cmdhash{$cmd})) { | |
740 | return if flood_check($user); | |
741 | ||
742 | &{$cmdhash{$cmd}}($user, $chan, $cmd, $msg, @args); | |
743 | } | |
744 | } | |
745 | ||
746 | sub bot_say($$$) { | |
747 | my ($user, $chan, $botmsg) = @_; | |
748 | my $cn = $chan->{CHAN}; | |
749 | ||
750 | if(chanserv::can_do($chan, 'BotSay', $user)) { | |
751 | ircd::notice(agent($chan), '%'.$cn, get_user_nick($user).' used BotSay') | |
752 | if cr_chk_flag($chan, CRF_VERBOSE()); | |
753 | ircd::privmsg(agent($chan), $cn, $botmsg); | |
754 | } else { | |
755 | # can_do will give the $err_deny for us. | |
756 | #notice($user, $err_deny); | |
757 | } | |
758 | } | |
759 | ||
760 | ### BOT COMMANDS ### | |
761 | ||
762 | sub bot_dispatch($$$) { | |
5e682044 | 763 | my ($user, $bot, $msg) = @_; |
aecfa1fd | 764 | my ($cmd, $cn, $botmsg) = split(/ /, $msg, 3); |
5e682044 | 765 | my $src = $user->{NICK}; |
aecfa1fd | 766 | my $chan = { CHAN => $cn }; |
aecfa1fd | 767 | return if flood_check($user); |
aecfa1fd | 768 | if ($cmd =~ /^join$/i) { |
5e682044 | 769 | if (adminserv::can_do($user, 'BOT')) { |
770 | agent_join($bot, $cn); | |
771 | } else { | |
772 | notice($user, $err_deny); | |
773 | } | |
aecfa1fd | 774 | } |
775 | elsif ($cmd =~ /^part$/i) { | |
5e682044 | 776 | if (adminserv::can_do($user, 'BOT')) { |
777 | agent_part($bot, $cn, "$src requested part"); | |
778 | } else { | |
779 | notice($user, $err_deny); | |
780 | } | |
aecfa1fd | 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 | ||
794 | sub 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 | ||
826 | sub chan_msg($$$) { | |
827 | #We don't do chanmsg processing yet, like badwords. | |
828 | } | |
829 | ||
830 | sub 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); | |
7b3a5814 | 835 | my $bot = { NICK => $nick, ID=>ircd::getAgentUuid($nick) }; |
aecfa1fd | 836 | agent_join($nick, main_conf_diag); |
5e682044 | 837 | my $rsuser = { NICK => $main::rsnick }; |
838 | get_user_id ($rsuser); | |
5e682044 | 839 | ircd::setmode($rsuser, main_conf_diag, '+h', $bot); |
aecfa1fd | 840 | } |
841 | } | |
842 | ||
843 | sub eos() { | |
844 | $get_botchans->execute(); | |
845 | while(my ($cn, $nick) = $get_botchans->fetchrow_array) { | |
846 | my $chan = { CHAN => $cn }; | |
847 | if(chanserv::get_user_count($chan)) { | |
7b3a5814 | 848 | my $botUser = { NICK=>$nick }; |
849 | get_user_id ($botUser); | |
5e682044 | 850 | bot_join($chan, $botUser); |
aecfa1fd | 851 | } |
852 | elsif(cr_chk_flag($chan, CRF_BOTSTAY(), 1)) { | |
7b3a5814 | 853 | my $botUser = { NICK=>$nick, ID=>ircd::getAgentUuid($nick) }; |
92c29160 | 854 | bot_join($chan, $botUser); |
aecfa1fd | 855 | my $modelock = chanserv::get_modelock($chan); |
856 | ircd::setmode(main_conf_local, $cn, $modelock) if $modelock; | |
857 | } | |
858 | } | |
859 | } | |
860 | ||
861 | ### Database Functions ### | |
862 | ||
863 | sub set_flag($$) { | |
864 | my ($bot, $flag) = @_; | |
865 | ||
866 | $set_flag->execute($flag, $bot); | |
867 | } | |
868 | ||
869 | sub unset_flag($$) { | |
870 | my ($bot, $flag) = @_; | |
871 | ||
872 | $unset_flag->execute($flag, $bot); | |
873 | } | |
874 | ||
875 | sub bot_join($;$) { | |
5e682044 | 876 | my ($chan, $bot) = @_; |
aecfa1fd | 877 | my $cn = $chan->{CHAN}; |
5e682044 | 878 | $bot = agent($chan) unless $bot; |
879 | my $nick = $bot->{NICK}; | |
aecfa1fd | 880 | unless(is_agent_in_chan($nick, $cn)) { |
5e682044 | 881 | agent_join($bot, $cn); |
7b3a5814 | 882 | my $bot2 = { NICK => $nick, ID => ircd::getAgentUuid($nick), UID=>ircd::getAgentUuid($nick) }; |
88eba747 | 883 | if(!ircd::PREFIXAQ_DISABLE() && $IRCd_capabilities{"FOUNDER"} ne "" && $IRCd_capabilities{"ADMIN"} ne "") { |
884 | $botchmode = '+q'; | |
885 | } else { | |
886 | $botchmode = '+o'; | |
887 | } | |
a1a0e944 | 888 | ircd::setmode($bot2, $cn, $botchmode, $bot2 ); |
aecfa1fd | 889 | } |
890 | } | |
891 | ||
892 | sub bot_part_if_needed($$$;$) { | |
47ce1466 | 893 | my ($u, $chan, $reason, $empty) = @_; |
aecfa1fd | 894 | my $cn = $chan->{CHAN}; |
895 | my $bot = get_chan_bot($chan); | |
47ce1466 | 896 | $u = agent($chan) unless $u; |
897 | my $nick = $u->{NICK}; | |
aecfa1fd | 898 | return if (lc $chanserv::enforcers{lc $cn} eq lc $nick); |
aecfa1fd | 899 | if(is_agent_in_chan($nick, $cn)) { |
900 | if(lc $bot eq lc $nick) { | |
901 | if(cr_chk_flag($chan, CRF_BOTSTAY(), 1) or ($empty != 1 or chanserv::get_user_count($chan))) { | |
902 | return; | |
903 | } | |
904 | } | |
aecfa1fd | 905 | agent_part($nick, $cn, $reason); |
906 | } | |
907 | } | |
908 | ||
909 | sub get_chan_bot($) { | |
910 | my ($chan) = @_; | |
911 | my $cn = $chan->{CHAN}; | |
912 | $botserv::get_chan_bot->execute($cn); | |
913 | ||
914 | my ($bot) = $botserv::get_chan_bot->fetchrow_array(); | |
915 | $botserv::get_chan_bot->finish(); | |
916 | ||
917 | return $bot; | |
918 | } | |
919 | ||
920 | 1; |