00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 #include "module.h"
00016
00017 class CommandBSKick : public Command
00018 {
00019 public:
00020 CommandBSKick(Module *creator) : Command(creator, "botserv/kick", 3, 6)
00021 {
00022 this->SetDesc(_("Configures kickers"));
00023 this->SetSyntax(_("\037channel\037 \037option\037 {\037ON|OFF\037} [\037settings\037]"));
00024 }
00025
00026 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override
00027 {
00028 const Anope::string &chan = params[0];
00029 const Anope::string &option = params[1];
00030 const Anope::string &value = params[2];
00031 const Anope::string &ttb = params.size() > 3 ? params[3] : "";
00032
00033 ChannelInfo *ci = ChannelInfo::Find(params[0]);
00034
00035 if (Anope::ReadOnly)
00036 source.Reply(_("Sorry, kicker configuration is temporarily disabled."));
00037 else if (ci == NULL)
00038 source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
00039 else if (chan.empty() || option.empty() || value.empty())
00040 this->OnSyntaxError(source, "");
00041 else if (!value.equals_ci("ON") && !value.equals_ci("OFF"))
00042 this->OnSyntaxError(source, "");
00043 else if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("botserv/administration"))
00044 source.Reply(ACCESS_DENIED);
00045 else if (!ci->bi)
00046 source.Reply(BOT_NOT_ASSIGNED);
00047 else
00048 {
00049 bool override = !source.AccessFor(ci).HasPriv("SET");
00050 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << option << " " << value;
00051
00052 if (option.equals_ci("BADWORDS"))
00053 {
00054 if (value.equals_ci("ON"))
00055 {
00056 if (!ttb.empty())
00057 {
00058 try
00059 {
00060 ci->ttb[TTB_BADWORDS] = convertTo<int16_t>(ttb);
00061 if (ci->ttb[TTB_BADWORDS] < 0)
00062 throw ConvertException();
00063 }
00064 catch (const ConvertException &)
00065 {
00066
00067 ci->ttb[TTB_BADWORDS] = 0;
00068 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00069 return;
00070 }
00071 }
00072 else
00073 ci->ttb[TTB_BADWORDS] = 0;
00074
00075 ci->ExtendMetadata("BS_KICK_BADWORDS");
00076 if (ci->ttb[TTB_BADWORDS])
00077 source.Reply(_("Bot will now kick for \002bad words\002, and will place a ban\n"
00078 "after %d kicks for the same user. Use the BADWORDS command\n"
00079 "to add or remove a bad word."), ci->ttb[TTB_BADWORDS]);
00080 else
00081 source.Reply(_("Bot will now kick for \002bad words\002. Use the BADWORDS command\n"
00082 "to add or remove a bad word."));
00083 }
00084 else
00085 {
00086 ci->Shrink("BS_KICK_BADWORDS");
00087 source.Reply(_("Bot won't kick for \002bad words\002 anymore."));
00088 }
00089 }
00090 else if (option.equals_ci("BOLDS"))
00091 {
00092 if (value.equals_ci("ON"))
00093 {
00094 if (!ttb.empty())
00095 {
00096 try
00097 {
00098 ci->ttb[TTB_BOLDS] = convertTo<int16_t>(ttb);
00099 if (ci->ttb[TTB_BOLDS] < 0)
00100 throw ConvertException();
00101 }
00102 catch (const ConvertException &)
00103 {
00104 ci->ttb[TTB_BOLDS] = 0;
00105 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00106 return;
00107 }
00108 }
00109 else
00110 ci->ttb[TTB_BOLDS] = 0;
00111 ci->ExtendMetadata("BS_KICK_BOLDS");
00112 if (ci->ttb[TTB_BOLDS])
00113 source.Reply(_("Bot will now kick for \002bolds\002, and will place a ban\n"
00114 "after %d kicks for the same user."), ci->ttb[TTB_BOLDS]);
00115 else
00116 source.Reply(_("Bot will now kick for \002bolds\002."));
00117 }
00118 else
00119 {
00120 ci->Shrink("BS_KICK_BOLDS");
00121 source.Reply(_("Bot won't kick for \002bolds\002 anymore."));
00122 }
00123 }
00124 else if (option.equals_ci("CAPS"))
00125 {
00126 if (value.equals_ci("ON"))
00127 {
00128 Anope::string min = params.size() > 4 ? params[4] : "";
00129 Anope::string percent = params.size() > 5 ? params[5] : "";
00130
00131 if (!ttb.empty())
00132 {
00133 try
00134 {
00135 ci->ttb[TTB_CAPS] = convertTo<int16_t>(ttb);
00136 if (ci->ttb[TTB_CAPS] < 0)
00137 throw ConvertException();
00138 }
00139 catch (const ConvertException &)
00140 {
00141 ci->ttb[TTB_CAPS] = 0;
00142 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00143 return;
00144 }
00145 }
00146 else
00147 ci->ttb[TTB_CAPS] = 0;
00148
00149 ci->capsmin = 10;
00150 try
00151 {
00152 ci->capsmin = convertTo<int16_t>(min);
00153 }
00154 catch (const ConvertException &) { }
00155 if (ci->capsmin < 1)
00156 ci->capsmin = 10;
00157
00158 ci->capspercent = 25;
00159 try
00160 {
00161 ci->capspercent = convertTo<int16_t>(percent);
00162 }
00163 catch (const ConvertException &) { }
00164 if (ci->capspercent < 1 || ci->capspercent > 100)
00165 ci->capspercent = 25;
00166
00167 ci->ExtendMetadata("BS_KICK_CAPS");
00168 if (ci->ttb[TTB_CAPS])
00169 source.Reply(_("Bot will now kick for \002caps\002 (they must constitute at least\n"
00170 "%d characters and %d%% of the entire message), and will\n"
00171 "place a ban after %d kicks for the same user."), ci->capsmin, ci->capspercent, ci->ttb[TTB_CAPS]);
00172 else
00173 source.Reply(_("Bot will now kick for \002caps\002 (they must constitute at least\n"
00174 "%d characters and %d%% of the entire message)."), ci->capsmin, ci->capspercent);
00175 }
00176 else
00177 {
00178 ci->Shrink("BS_KICK_CAPS");
00179 source.Reply(_("Bot won't kick for \002caps\002 anymore."));
00180 }
00181 }
00182 else if (option.equals_ci("COLORS"))
00183 {
00184 if (value.equals_ci("ON"))
00185 {
00186 if (!ttb.empty())
00187 {
00188 try
00189 {
00190 ci->ttb[TTB_COLORS] = convertTo<int16_t>(ttb);
00191 if (ci->ttb[TTB_COLORS] < 1)
00192 throw ConvertException();
00193 }
00194 catch (const ConvertException &)
00195 {
00196 ci->ttb[TTB_COLORS] = 0;
00197 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00198 return;
00199 }
00200 }
00201 else
00202 ci->ttb[TTB_COLORS] = 0;
00203
00204 ci->ExtendMetadata("BS_KICK_COLORS");
00205 if (ci->ttb[TTB_COLORS])
00206 source.Reply(_("Bot will now kick for \002colors\002, and will place a ban\n"
00207 "after %d kicks for the same user."), ci->ttb[TTB_COLORS]);
00208 else
00209 source.Reply(_("Bot will now kick for \002colors\002."));
00210 }
00211 else
00212 {
00213 ci->Shrink("BS_KICK_COLORS");
00214 source.Reply(_("Bot won't kick for \002colors\002 anymore."));
00215 }
00216 }
00217 else if (option.equals_ci("FLOOD"))
00218 {
00219 if (value.equals_ci("ON"))
00220 {
00221 Anope::string lines = params.size() > 4 ? params[4] : "";
00222 Anope::string secs = params.size() > 5 ? params[5] : "";
00223
00224 if (!ttb.empty())
00225 {
00226 try
00227 {
00228 ci->ttb[TTB_FLOOD] = convertTo<int16_t>(ttb);
00229 if (ci->ttb[TTB_FLOOD] < 1)
00230 throw ConvertException();
00231 }
00232 catch (const ConvertException &)
00233 {
00234 ci->ttb[TTB_FLOOD] = 0;
00235 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00236 return;
00237 }
00238 }
00239 else
00240 ci->ttb[TTB_FLOOD] = 0;
00241
00242 ci->floodlines = 6;
00243 try
00244 {
00245 ci->floodlines = convertTo<int16_t>(lines);
00246 }
00247 catch (const ConvertException &) { }
00248 if (ci->floodlines < 2)
00249 ci->floodlines = 6;
00250
00251 ci->floodsecs = 10;
00252 try
00253 {
00254 ci->floodsecs = convertTo<int16_t>(secs);
00255 }
00256 catch (const ConvertException &) { }
00257 if (ci->floodsecs < 1)
00258 ci->floodsecs = 10;
00259 if (ci->floodsecs > Config->BSKeepData)
00260 ci->floodsecs = Config->BSKeepData;
00261
00262 ci->ExtendMetadata("BS_KICK_FLOOD");
00263 if (ci->ttb[TTB_FLOOD])
00264 source.Reply(_("Bot will now kick for \002flood\002 (%d lines in %d seconds\n"
00265 "and will place a ban after %d kicks for the same user."), ci->floodlines, ci->floodsecs, ci->ttb[TTB_FLOOD]);
00266 else
00267 source.Reply(_("Bot will now kick for \002flood\002 (%d lines in %d seconds)."), ci->floodlines, ci->floodsecs);
00268 }
00269 else
00270 {
00271 ci->Shrink("BS_KICK_FLOOD");
00272 source.Reply(_("Bot won't kick for \002flood\002 anymore."));
00273 }
00274 }
00275 else if (option.equals_ci("REPEAT"))
00276 {
00277 if (value.equals_ci("ON"))
00278 {
00279 Anope::string times = params.size() > 4 ? params[4] : "";
00280
00281 if (!ttb.empty())
00282 {
00283 try
00284 {
00285 ci->ttb[TTB_REPEAT] = convertTo<int16_t>(ttb);
00286 if (ci->ttb[TTB_REPEAT] < 0)
00287 throw ConvertException();
00288 }
00289 catch (const ConvertException &)
00290 {
00291 ci->ttb[TTB_REPEAT] = 0;
00292 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00293 return;
00294 }
00295 }
00296 else
00297 ci->ttb[TTB_REPEAT] = 0;
00298
00299 ci->repeattimes = 3;
00300 try
00301 {
00302 ci->repeattimes = convertTo<int16_t>(times);
00303 }
00304 catch (const ConvertException &) { }
00305 if (ci->repeattimes < 2)
00306 ci->repeattimes = 3;
00307
00308 ci->ExtendMetadata("BS_KICK_REPEAT");
00309 if (ci->ttb[TTB_REPEAT])
00310 source.Reply(_("Bot will now kick for \002repeats\002 (users that say the\n"
00311 "same thing %d times), and will place a ban after %d\n"
00312 "kicks for the same user."), ci->repeattimes, ci->ttb[TTB_REPEAT]);
00313 else
00314 source.Reply(_("Bot will now kick for \002repeats\002 (users that say the\n"
00315 "same thing %d times)."), ci->repeattimes);
00316 }
00317 else
00318 {
00319 ci->Shrink("BS_KICK_REPEAT");
00320 source.Reply(_("Bot won't kick for \002repeats\002 anymore."));
00321 }
00322 }
00323 else if (option.equals_ci("REVERSES"))
00324 {
00325 if (value.equals_ci("ON"))
00326 {
00327 if (!ttb.empty())
00328 {
00329 try
00330 {
00331 ci->ttb[TTB_REVERSES] = convertTo<int16_t>(ttb);
00332 if (ci->ttb[TTB_REVERSES] < 0)
00333 throw ConvertException();
00334 }
00335 catch (const ConvertException &)
00336 {
00337 ci->ttb[TTB_REVERSES] = 0;
00338 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00339 return;
00340 }
00341 }
00342 else
00343 ci->ttb[TTB_REVERSES] = 0;
00344 ci->ExtendMetadata("BS_KICK_REVERSES");
00345 if (ci->ttb[TTB_REVERSES])
00346 source.Reply(_("Bot will now kick for \002reverses\002, and will place a ban\n"
00347 "after %d kicks for the same user."), ci->ttb[TTB_REVERSES]);
00348 else
00349 source.Reply(_("Bot will now kick for \002reverses\002."));
00350 }
00351 else
00352 {
00353 ci->Shrink("BS_KICK_REVERSES");
00354 source.Reply(_("Bot won't kick for \002reverses\002 anymore."));
00355 }
00356 }
00357 else if (option.equals_ci("UNDERLINES"))
00358 {
00359 if (value.equals_ci("ON"))
00360 {
00361 if (!ttb.empty())
00362 {
00363 try
00364 {
00365 ci->ttb[TTB_UNDERLINES] = convertTo<int16_t>(ttb);
00366 if (ci->ttb[TTB_REVERSES] < 0)
00367 throw ConvertException();
00368 }
00369 catch (const ConvertException &)
00370 {
00371 ci->ttb[TTB_UNDERLINES] = 0;
00372 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00373 return;
00374 }
00375 }
00376 else
00377 ci->ttb[TTB_UNDERLINES] = 0;
00378
00379 ci->ExtendMetadata("BS_KICK_UNDERLINES");
00380 if (ci->ttb[TTB_UNDERLINES])
00381 source.Reply(_("Bot will now kick for \002underlines\002, and will place a ban\n"
00382 "after %d kicks for the same user."), ci->ttb[TTB_UNDERLINES]);
00383 else
00384 source.Reply(_("Bot will now kick for \002underlines\002."));
00385 }
00386 else
00387 {
00388 ci->Shrink("BS_KICK_UNDERLINES");
00389 source.Reply(_("Bot won't kick for \002underlines\002 anymore."));
00390 }
00391 }
00392 else if (option.equals_ci("ITALICS"))
00393 {
00394 if (value.equals_ci("ON"))
00395 {
00396 if (!ttb.empty())
00397 {
00398 try
00399 {
00400 ci->ttb[TTB_ITALICS] = convertTo<int16_t>(ttb);
00401 if (ci->ttb[TTB_ITALICS] < 0)
00402 throw ConvertException();
00403 }
00404 catch (const ConvertException &)
00405 {
00406 ci->ttb[TTB_ITALICS] = 0;
00407 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00408 return;
00409 }
00410 }
00411 else
00412 ci->ttb[TTB_ITALICS] = 0;
00413
00414 ci->ExtendMetadata("BS_KICK_ITALICS");
00415 if (ci->ttb[TTB_ITALICS])
00416 source.Reply(_("Bot will now kick for \002italics\002, and will place a ban\n"
00417 "after %d kicks for the same user."), ci->ttb[TTB_ITALICS]);
00418 else
00419 source.Reply(_("Bot will now kick for \002italics\002."));
00420 }
00421 else
00422 {
00423 ci->Shrink("BS_KICK_ITALICS");
00424 source.Reply(_("Bot won't kick for \002italics\002 anymore."));
00425 }
00426 }
00427 else if (option.equals_ci("AMSGS"))
00428 {
00429 if (value.equals_ci("ON"))
00430 {
00431 if (!ttb.empty())
00432 {
00433 try
00434 {
00435 ci->ttb[TTB_AMSGS] = convertTo<int16_t>(ttb);
00436 if (ci->ttb[TTB_AMSGS] < 0)
00437 throw ConvertException();
00438 }
00439 catch (const ConvertException &)
00440 {
00441 ci->ttb[TTB_AMSGS] = 0;
00442 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str());
00443 return;
00444 }
00445 }
00446 else
00447 ci->ttb[TTB_AMSGS] = 0;
00448
00449 ci->ExtendMetadata("BS_KICK_AMSGS");
00450 if (ci->ttb[TTB_AMSGS])
00451 source.Reply(_("Bot will now kick for \002amsgs\002, and will place a ban\n"
00452 "after %d kicks for the same user."), ci->ttb[TTB_AMSGS]);
00453 else
00454 source.Reply(_("Bot will now kick for \002amsgs\002"));
00455 }
00456 else
00457 {
00458 ci->Shrink("BS_KICK_AMSGS");
00459 source.Reply(_("Bot won't kick for \002amsgs\002 anymore."));
00460 }
00461 }
00462 else
00463 this->OnSyntaxError(source, "");
00464 }
00465 return;
00466 }
00467
00468 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00469 {
00470 if (subcommand.empty())
00471 {
00472 this->SendSyntax(source);
00473 source.Reply(" ");
00474 source.Reply(_("Configures bot kickers. \037option\037 can be one of:\n"
00475 " \n"
00476 " AMSGS Sets if the bot kicks for amsgs\n"
00477 " BOLDS Sets if the bot kicks bolds\n"
00478 " BADWORDS Sets if the bot kicks bad words\n"
00479 " CAPS Sets if the bot kicks caps\n"
00480 " COLORS Sets if the bot kicks colors\n"
00481 " FLOOD Sets if the bot kicks flooding users\n"
00482 " REPEAT Sets if the bot kicks users who repeat\n"
00483 " themselves\n"
00484 " REVERSES Sets if the bot kicks reverses\n"
00485 " UNDERLINES Sets if the bot kicks underlines\n"
00486 " ITALICS Sets if the bot kicks italics\n"
00487 " \n"
00488 "Type \002%s%s HELP KICK \037option\037\002 for more information\n"
00489 "on a specific option.\n"
00490 " \n"
00491 "Note: access to this command is controlled by the\n"
00492 "level SET."), Config->UseStrictPrivMsgString.c_str(), source.service->nick.c_str());
00493 }
00494 else if (subcommand.equals_ci("BADWORDS"))
00495 source.Reply(_("Syntax: \002\037channel\037 BADWORDS {\037ON|OFF\037} [\037ttb\037]\002\n"
00496 "Sets the bad words kicker on or off. When enabled, this\n"
00497 "option tells the bot to kick users who say certain words\n"
00498 "on the channels.\n"
00499 "You can define bad words for your channel using the\n"
00500 "\002BADWORDS\002 command. Type \002%s%s HELP BADWORDS\002 for\n"
00501 "more information.\n"
00502 "ttb is the number of times a user can be kicked\n"
00503 "before it get banned. Don't give ttb to disable\n"
00504 "the ban system once activated."), Config->UseStrictPrivMsgString.c_str(), source.service->nick.c_str());
00505 else if (subcommand.equals_ci("BOLDS"))
00506 source.Reply(_("Syntax: \002\037channel\037 BOLDS {\037ON|OFF\037} [\037ttb\037]\002\n"
00507 "Sets the bolds kicker on or off. When enabled, this\n"
00508 "option tells the bot to kick users who use bolds.\n"
00509 "ttb is the number of times a user can be kicked\n"
00510 "before it get banned. Don't give ttb to disable\n"
00511 "the ban system once activated."));
00512 else if (subcommand.equals_ci("CAPS"))
00513 source.Reply(_("Syntax: \002\037channel\037 CAPS {\037ON|OFF\037} [\037ttb\037 [\037min\037 [\037percent\037]]]\002\n"
00514 "Sets the caps kicker on or off. When enabled, this\n"
00515 "option tells the bot to kick users who are talking in\n"
00516 "CAPS.\n"
00517 "The bot kicks only if there are at least \002min\002 caps\n"
00518 "and they constitute at least \002percent\002%% of the total\n"
00519 "text line (if not given, it defaults to 10 characters\n"
00520 "and 25%%).\n"
00521 "ttb is the number of times a user can be kicked\n"
00522 "before it get banned. Don't give ttb to disable\n"
00523 "the ban system once activated."));
00524 else if (subcommand.equals_ci("COLORS"))
00525 source.Reply(_("Syntax: \002\037channel\037 COLORS {\037ON|OFF\037} [\037ttb\037]\002\n"
00526 "Sets the colors kicker on or off. When enabled, this\n"
00527 "option tells the bot to kick users who use colors.\n"
00528 "ttb is the number of times a user can be kicked\n"
00529 "before it get banned. Don't give ttb to disable\n"
00530 "the ban system once activated."));
00531 else if (subcommand.equals_ci("FLOOD"))
00532 source.Reply(_("Syntax: \002\037channel\037 FLOOD {\037ON|OFF\037} [\037ttb\037 [\037ln\037 [\037secs\037]]]\002\n"
00533 "Sets the flood kicker on or off. When enabled, this\n"
00534 "option tells the bot to kick users who are flooding\n"
00535 "the channel using at least \002ln\002 lines in \002secs\002 seconds\n"
00536 "(if not given, it defaults to 6 lines in 10 seconds).\n"
00537 " \n"
00538 "ttb is the number of times a user can be kicked\n"
00539 "before it get banned. Don't give ttb to disable\n"
00540 "the ban system once activated."));
00541 else if (subcommand.equals_ci("REPEAT"))
00542 source.Reply(_("Syntax: \002\037#channel\037 REPEAT {\037ON|OFF\037} [\037ttb\037 [\037num\037]]\002\n"
00543 "Sets the repeat kicker on or off. When enabled, this\n"
00544 "option tells the bot to kick users who are repeating\n"
00545 "themselves \002num\002 times (if num is not given, it\n"
00546 "defaults to 3).\n"
00547 "ttb is the number of times a user can be kicked\n"
00548 "before it get banned. Don't give ttb to disable\n"
00549 "the ban system once activated."));
00550 else if (subcommand.equals_ci("REVERSES"))
00551 source.Reply(_("Syntax: \002\037channel\037 REVERSES {\037ON|OFF\037} [\037ttb\037]\002\n"
00552 "Sets the reverses kicker on or off. When enabled, this\n"
00553 "option tells the bot to kick users who use reverses.\n"
00554 "ttb is the number of times a user can be kicked\n"
00555 "before it get banned. Don't give ttb to disable\n"
00556 "the ban system once activated."));
00557 else if (subcommand.equals_ci("UNDERLINES"))
00558 source.Reply(_("Syntax: \002\037channel\037 UNDERLINES {\037ON|OFF\037} [\037ttb\037]\002\n"
00559 "Sets the underlines kicker on or off. When enabled, this\n"
00560 "option tells the bot to kick users who use underlines.\n"
00561 "ttb is the number of times a user can be kicked\n"
00562 "before it get banned. Don't give ttb to disable\n"
00563 "the ban system once activated."));
00564 else if (subcommand.equals_ci("ITALICS"))
00565 source.Reply(_("Syntax: \002\037channel\037 ITALICS {\037ON|OFF\037} [\037ttb\037]\002\n"
00566 "Sets the italics kicker on or off. When enabled, this\n"
00567 "option tells the bot to kick users who use italics.\n"
00568 "ttb is the number of times a user can be kicked\n"
00569 "before it get banned. Don't give ttb to disable\n"
00570 "the ban system once activated."));
00571 else if (subcommand.equals_ci("AMSGS"))
00572 source.Reply(_("Syntax: \002\037channel\037 AMSGS {\037ON|OFF\037} [\037ttb\037]\002\n"
00573 "Sets the amsg kicker on or off. When enabled, the bot will\n"
00574 "kick users who send the same message to multiple channels\n"
00575 "where BotServ bots are.\n"
00576 "Ttb is the number of times a user can be kicked\n"
00577 "before it get banned. Don't give ttb to disable\n"
00578 "the ban system once activated."));
00579 else
00580 return false;
00581
00582 return true;
00583 }
00584 };
00585
00586 struct BanData : public ExtensibleItem
00587 {
00588 struct Data
00589 {
00590 Anope::string mask;
00591 time_t last_use;
00592 int16_t ttb[TTB_SIZE];
00593
00594 Data()
00595 {
00596 this->Clear();
00597 }
00598
00599 void Clear()
00600 {
00601 last_use = 0;
00602 for (int i = 0; i < TTB_SIZE; ++i)
00603 this->ttb[i] = 0;
00604 }
00605 };
00606
00607 private:
00608 typedef std::map<Anope::string, Data, ci::less> data_type;
00609 data_type data_map;
00610
00611 public:
00612 Data &get(const Anope::string &key)
00613 {
00614 return this->data_map[key];
00615 }
00616
00617 bool empty() const
00618 {
00619 return this->data_map.empty();
00620 }
00621
00622 void purge()
00623 {
00624 for (data_type::iterator it = data_map.begin(), it_end = data_map.end(); it != it_end;)
00625 {
00626 const Anope::string &user = it->first;
00627 Data &bd = it->second;
00628 ++it;
00629
00630 if (Anope::CurTime - bd.last_use > Config->BSKeepData)
00631 data_map.erase(user);
00632 }
00633 }
00634 };
00635
00636 struct UserData : public ExtensibleItem
00637 {
00638 UserData()
00639 {
00640 this->Clear();
00641 }
00642
00643 void Clear()
00644 {
00645 last_use = last_start = Anope::CurTime;
00646 lines = times = 0;
00647 lastline.clear();
00648 }
00649
00650
00651 time_t last_use;
00652
00653
00654 int16_t lines;
00655 time_t last_start;
00656
00657
00658 Anope::string lasttarget;
00659 int16_t times;
00660
00661 Anope::string lastline;
00662 };
00663
00664
00665 class BanDataPurger : public CallBack
00666 {
00667 public:
00668 BanDataPurger(Module *owner) : CallBack(owner, 300, Anope::CurTime, true) { }
00669
00670 void Tick(time_t) anope_override
00671 {
00672 Log(LOG_DEBUG) << "bs_main: Running bandata purger";
00673
00674 for (channel_map::iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it)
00675 {
00676 Channel *c = it->second;
00677
00678 BanData *bd = c->GetExt<BanData *>("bs_main_bandata");
00679 if (bd != NULL)
00680 {
00681 bd->purge();
00682 if (bd->empty())
00683 c->Shrink("bs_main_bandata");
00684 }
00685 }
00686 }
00687 };
00688
00689 class BSKick : public Module
00690 {
00691 CommandBSKick commandbskick;
00692 BanDataPurger purger;
00693
00694 BanData::Data &GetBanData(User *u, Channel *c)
00695 {
00696 BanData *bd = c->GetExt<BanData *>("bs_main_bandata");
00697 if (bd == NULL)
00698 {
00699 bd = new BanData();
00700 c->Extend("bs_main_bandata", bd);
00701 }
00702
00703 return bd->get(u->GetMask());
00704 }
00705
00706 UserData *GetUserData(User *u, Channel *c)
00707 {
00708 ChanUserContainer *uc = c->FindUser(u);
00709 if (uc == NULL)
00710 return NULL;
00711
00712 UserData *ud = uc->GetExt<UserData *>("bs_main_userdata");
00713 if (ud == NULL)
00714 {
00715 ud = new UserData();
00716 uc->Extend("bs_main_userdata", ud);
00717 }
00718
00719 return ud;
00720 }
00721
00722 void check_ban(ChannelInfo *ci, User *u, int ttbtype)
00723 {
00724
00725 if (u->server->IsULined())
00726 return;
00727
00728 BanData::Data &bd = this->GetBanData(u, ci->c);
00729
00730 ++bd.ttb[ttbtype];
00731 if (ci->ttb[ttbtype] && bd.ttb[ttbtype] >= ci->ttb[ttbtype])
00732 {
00733
00734
00735
00736
00737 bd.ttb[ttbtype] = 0;
00738
00739 Anope::string mask = ci->GetIdealBan(u);
00740
00741 ci->c->SetMode(NULL, "BAN", mask);
00742 FOREACH_MOD(I_OnBotBan, OnBotBan(u, ci, mask));
00743 }
00744 }
00745
00746 void bot_kick(ChannelInfo *ci, User *u, const char *message, ...)
00747 {
00748 va_list args;
00749 char buf[1024];
00750
00751 if (!ci || !ci->bi || !ci->c || !u || u->server->IsULined() || !ci->c->FindUser(u))
00752 return;
00753
00754 Anope::string fmt = Language::Translate(u, message);
00755 va_start(args, message);
00756 vsnprintf(buf, sizeof(buf), fmt.c_str(), args);
00757 va_end(args);
00758
00759 ci->c->Kick(ci->bi, u, "%s", buf);
00760 }
00761
00762 public:
00763 BSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE),
00764 commandbskick(this), purger(this)
00765 {
00766 this->SetAuthor("Anope");
00767
00768 ModuleManager::Attach(I_OnPrivmsg, this);
00769 }
00770
00771 ~BSKick()
00772 {
00773 for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit)
00774 {
00775 Channel *c = cit->second;
00776 for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it)
00777 (*it)->Shrink("bs_main_userdata");
00778 c->Shrink("bs_main_bandata");
00779 }
00780 }
00781
00782 void OnPrivmsg(User *u, Channel *c, Anope::string &msg) anope_override
00783 {
00784
00785
00786
00787
00788
00789
00790
00791
00792 ChannelInfo *ci = c->ci;
00793 if (ci == NULL)
00794 return;
00795
00796 if (ci->AccessFor(u).HasPriv("NOKICK"))
00797 return;
00798 else if (ci->HasExt("BS_DONTKICKOPS") && (c->HasUserStatus(u, "HALFOP") || c->HasUserStatus(u, "OP") || c->HasUserStatus(u, "PROTECT") || c->HasUserStatus(u, "OWNER")))
00799 return;
00800 else if (ci->HasExt("BS_DONTKICKVOICES") && c->HasUserStatus(u, "VOICE"))
00801 return;
00802
00803 Anope::string realbuf = msg;
00804
00805
00806
00807
00808 if (realbuf.substr(0, 8).equals_ci("\1ACTION ") && realbuf[realbuf.length() - 1] == '\1')
00809 {
00810 realbuf.erase(0, 8);
00811 realbuf.erase(realbuf.length() - 1);
00812 }
00813
00814 if (realbuf.empty())
00815 return;
00816
00817
00818 if (ci->HasExt("BS_KICK_BOLDS") && realbuf.find(2) != Anope::string::npos)
00819 {
00820 check_ban(ci, u, TTB_BOLDS);
00821 bot_kick(ci, u, _("Don't use bolds on this channel!"));
00822 return;
00823 }
00824
00825
00826 if (ci->HasExt("BS_KICK_COLORS") && realbuf.find(3) != Anope::string::npos)
00827 {
00828 check_ban(ci, u, TTB_COLORS);
00829 bot_kick(ci, u, _("Don't use colors on this channel!"));
00830 return;
00831 }
00832
00833
00834 if (ci->HasExt("BS_KICK_REVERSES") && realbuf.find(22) != Anope::string::npos)
00835 {
00836 check_ban(ci, u, TTB_REVERSES);
00837 bot_kick(ci, u, _("Don't use reverses on this channel!"));
00838 return;
00839 }
00840
00841
00842 if (ci->HasExt("BS_KICK_ITALICS") && realbuf.find(29) != Anope::string::npos)
00843 {
00844 check_ban(ci, u, TTB_ITALICS);
00845 bot_kick(ci, u, _("Don't use italics on this channel!"));
00846 return;
00847 }
00848
00849
00850 if (ci->HasExt("BS_KICK_UNDERLINES") && realbuf.find(31) != Anope::string::npos)
00851 {
00852 check_ban(ci, u, TTB_UNDERLINES);
00853 bot_kick(ci, u, _("Don't use underlines on this channel!"));
00854 return;
00855 }
00856
00857
00858 if (ci->HasExt("BS_KICK_CAPS") && realbuf.length() >= static_cast<unsigned>(ci->capsmin))
00859 {
00860 int i = 0, l = 0;
00861
00862 for (unsigned j = 0, end = realbuf.length(); j < end; ++j)
00863 {
00864 if (isupper(realbuf[j]))
00865 ++i;
00866 else if (islower(realbuf[j]))
00867 ++l;
00868 }
00869
00870
00871
00872
00873
00874
00875 if ((i || l) && i >= ci->capsmin && i * 100 / (i + l) >= ci->capspercent)
00876 {
00877 check_ban(ci, u, TTB_CAPS);
00878 bot_kick(ci, u, _("Turn caps lock OFF!"));
00879 return;
00880 }
00881 }
00882
00883
00884 if (ci->HasExt("BS_KICK_BADWORDS"))
00885 {
00886 bool mustkick = false;
00887
00888
00889 Anope::string nbuf = Anope::NormalizeBuffer(realbuf);
00890
00891 for (unsigned i = 0, end = ci->GetBadWordCount(); i < end; ++i)
00892 {
00893 const BadWord *bw = ci->GetBadWord(i);
00894
00895 if (bw->type == BW_ANY && ((Config->BSCaseSensitive && nbuf.find(bw->word) != Anope::string::npos) || (!Config->BSCaseSensitive && nbuf.find_ci(bw->word) != Anope::string::npos)))
00896 mustkick = true;
00897 else if (bw->type == BW_SINGLE)
00898 {
00899 size_t len = bw->word.length();
00900
00901 if ((Config->BSCaseSensitive && bw->word.equals_cs(nbuf)) || (!Config->BSCaseSensitive && bw->word.equals_ci(nbuf)))
00902 mustkick = true;
00903 else if (nbuf.find(' ') == len && ((Config->BSCaseSensitive && bw->word.equals_cs(nbuf)) || (!Config->BSCaseSensitive && bw->word.equals_ci(nbuf))))
00904 mustkick = true;
00905 else
00906 {
00907 if (nbuf.rfind(' ') == nbuf.length() - len - 1 && ((Config->BSCaseSensitive && nbuf.find(bw->word) == nbuf.length() - len) || (!Config->BSCaseSensitive && nbuf.find_ci(bw->word) == nbuf.length() - len)))
00908 mustkick = true;
00909 else
00910 {
00911 Anope::string wordbuf = " " + bw->word + " ";
00912
00913 if ((Config->BSCaseSensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!Config->BSCaseSensitive && nbuf.find_ci(wordbuf) != Anope::string::npos))
00914 mustkick = true;
00915 }
00916 }
00917 }
00918 else if (bw->type == BW_START)
00919 {
00920 size_t len = bw->word.length();
00921
00922 if ((Config->BSCaseSensitive && nbuf.substr(0, len).equals_cs(bw->word)) || (!Config->BSCaseSensitive && nbuf.substr(0, len).equals_ci(bw->word)))
00923 mustkick = true;
00924 else
00925 {
00926 Anope::string wordbuf = " " + bw->word;
00927
00928 if ((Config->BSCaseSensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!Config->BSCaseSensitive && nbuf.find_ci(wordbuf) != Anope::string::npos))
00929 mustkick = true;
00930 }
00931 }
00932 else if (bw->type == BW_END)
00933 {
00934 size_t len = bw->word.length();
00935
00936 if ((Config->BSCaseSensitive && nbuf.substr(nbuf.length() - len).equals_cs(bw->word)) || (!Config->BSCaseSensitive && nbuf.substr(nbuf.length() - len).equals_ci(bw->word)))
00937 mustkick = true;
00938 else
00939 {
00940 Anope::string wordbuf = bw->word + " ";
00941
00942 if ((Config->BSCaseSensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!Config->BSCaseSensitive && nbuf.find_ci(wordbuf) != Anope::string::npos))
00943 mustkick = true;
00944 }
00945 }
00946
00947 if (mustkick)
00948 {
00949 check_ban(ci, u, TTB_BADWORDS);
00950 if (Config->BSGentleBWReason)
00951 bot_kick(ci, u, _("Watch your language!"));
00952 else
00953 bot_kick(ci, u, _("Don't use the word \"%s\" on this channel!"), bw->word.c_str());
00954
00955 return;
00956 }
00957 }
00958 }
00959
00960 UserData *ud = GetUserData(u, c);
00961
00962 if (ud)
00963 {
00964
00965 if (ci->HasExt("BS_KICK_FLOOD"))
00966 {
00967 if (Anope::CurTime - ud->last_start > ci->floodsecs)
00968 {
00969 ud->last_start = Anope::CurTime;
00970 ud->lines = 0;
00971 }
00972
00973 ++ud->lines;
00974 if (ud->lines >= ci->floodlines)
00975 {
00976 check_ban(ci, u, TTB_FLOOD);
00977 bot_kick(ci, u, _("Stop flooding!"));
00978 return;
00979 }
00980 }
00981
00982
00983 if (ci->HasExt("BS_KICK_REPEAT"))
00984 {
00985 if (!ud->lastline.equals_ci(realbuf))
00986 ud->times = 0;
00987 else
00988 ++ud->times;
00989
00990 if (ud->times >= ci->repeattimes)
00991 {
00992 check_ban(ci, u, TTB_REPEAT);
00993 bot_kick(ci, u, _("Stop repeating yourself!"));
00994 return;
00995 }
00996 }
00997
00998 if (ud->lastline.equals_ci(realbuf) && !ud->lasttarget.empty() && !ud->lasttarget.equals_ci(ci->name))
00999 {
01000 for (User::ChanUserList::iterator it = u->chans.begin(); it != u->chans.end();)
01001 {
01002 Channel *chan = (*it)->chan;
01003 ++it;
01004
01005 if (chan->ci && chan->ci->HasExt("BS_KICK_AMSGS") && !chan->ci->AccessFor(u).HasPriv("NOKICK"))
01006 {
01007 check_ban(chan->ci, u, TTB_AMSGS);
01008 bot_kick(chan->ci, u, _("Don't use AMSGs!"));
01009 }
01010 }
01011 }
01012
01013 ud->lasttarget = ci->name;
01014 ud->lastline = realbuf;
01015 }
01016 }
01017 };
01018
01019 MODULE_INIT(BSKick)