anope- supernets anope source code & configuration |
git clone git://git.acid.vegas/anope.git |
Log | Files | Refs | Archive | README |
bs_kick.cpp (43142B)
1 /* BotServ core functions 2 * 3 * (C) 2003-2022 Anope Team 4 * Contact us at team@anope.org 5 * 6 * Please read COPYING and README for further details. 7 * 8 * Based on the original code of Epona by Lara. 9 * Based on the original code of Services by Andy Church. 10 */ 11 12 #include "module.h" 13 #include "modules/bs_kick.h" 14 #include "modules/bs_badwords.h" 15 16 static Module *me; 17 18 struct KickerDataImpl : KickerData 19 { 20 KickerDataImpl(Extensible *obj) 21 { 22 amsgs = badwords = bolds = caps = colors = flood = italics = repeat = reverses = underlines = false; 23 for (int16_t i = 0; i < TTB_SIZE; ++i) 24 ttb[i] = 0; 25 capsmin = capspercent = 0; 26 floodlines = floodsecs = 0; 27 repeattimes = 0; 28 29 dontkickops = dontkickvoices = false; 30 } 31 32 void Check(ChannelInfo *ci) anope_override 33 { 34 if (amsgs || badwords || bolds || caps || colors || flood || italics || repeat || reverses || underlines) 35 return; 36 37 ci->Shrink<KickerData>("kickerdata"); 38 } 39 40 struct ExtensibleItem : ::ExtensibleItem<KickerDataImpl> 41 { 42 ExtensibleItem(Module *m, const Anope::string &ename) : ::ExtensibleItem<KickerDataImpl>(m, ename) { } 43 44 void ExtensibleSerialize(const Extensible *e, const Serializable *s, Serialize::Data &data) const anope_override 45 { 46 if (s->GetSerializableType()->GetName() != "ChannelInfo") 47 return; 48 49 const ChannelInfo *ci = anope_dynamic_static_cast<const ChannelInfo *>(e); 50 KickerData *kd = this->Get(ci); 51 if (kd == NULL) 52 return; 53 54 data["kickerdata:amsgs"] << kd->amsgs; 55 data["kickerdata:badwords"] << kd->badwords; 56 data["kickerdata:bolds"] << kd->bolds; 57 data["kickerdata:caps"] << kd->caps; 58 data["kickerdata:colors"] << kd->colors; 59 data["kickerdata:flood"] << kd->flood; 60 data["kickerdata:italics"] << kd->italics; 61 data["kickerdata:repeat"] << kd->repeat; 62 data["kickerdata:reverses"] << kd->reverses; 63 data["kickerdata:underlines"] << kd->underlines; 64 65 data.SetType("capsmin", Serialize::Data::DT_INT); data["capsmin"] << kd->capsmin; 66 data.SetType("capspercent", Serialize::Data::DT_INT); data["capspercent"] << kd->capspercent; 67 data.SetType("floodlines", Serialize::Data::DT_INT); data["floodlines"] << kd->floodlines; 68 data.SetType("floodsecs", Serialize::Data::DT_INT); data["floodsecs"] << kd->floodsecs; 69 data.SetType("repeattimes", Serialize::Data::DT_INT); data["repeattimes"] << kd->repeattimes; 70 for (int16_t i = 0; i < TTB_SIZE; ++i) 71 data["ttb"] << kd->ttb[i] << " "; 72 } 73 74 void ExtensibleUnserialize(Extensible *e, Serializable *s, Serialize::Data &data) anope_override 75 { 76 if (s->GetSerializableType()->GetName() != "ChannelInfo") 77 return; 78 79 ChannelInfo *ci = anope_dynamic_static_cast<ChannelInfo *>(e); 80 KickerData *kd = ci->Require<KickerData>("kickerdata"); 81 82 data["kickerdata:amsgs"] >> kd->amsgs; 83 data["kickerdata:badwords"] >> kd->badwords; 84 data["kickerdata:bolds"] >> kd->bolds; 85 data["kickerdata:caps"] >> kd->caps; 86 data["kickerdata:colors"] >> kd->colors; 87 data["kickerdata:flood"] >> kd->flood; 88 data["kickerdata:italics"] >> kd->italics; 89 data["kickerdata:repeat"] >> kd->repeat; 90 data["kickerdata:reverses"] >> kd->reverses; 91 data["kickerdata:underlines"] >> kd->underlines; 92 93 data["capsmin"] >> kd->capsmin; 94 data["capspercent"] >> kd->capspercent; 95 data["floodlines"] >> kd->floodlines; 96 data["floodsecs"] >> kd->floodsecs; 97 data["repeattimes"] >> kd->repeattimes; 98 99 Anope::string ttb, tok; 100 data["ttb"] >> ttb; 101 spacesepstream sep(ttb); 102 for (int i = 0; sep.GetToken(tok) && i < TTB_SIZE; ++i) 103 try 104 { 105 kd->ttb[i] = convertTo<int16_t>(tok); 106 } 107 catch (const ConvertException &) { } 108 109 kd->Check(ci); 110 } 111 }; 112 }; 113 114 class CommandBSKick : public Command 115 { 116 public: 117 CommandBSKick(Module *creator) : Command(creator, "botserv/kick", 0) 118 { 119 this->SetDesc(_("Configures kickers")); 120 this->SetSyntax(_("\037option\037 \037channel\037 {\037ON|OFF\037} [\037settings\037]")); 121 } 122 123 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 124 { 125 this->OnSyntaxError(source, ""); 126 } 127 128 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 129 { 130 this->SendSyntax(source); 131 source.Reply(" "); 132 source.Reply(_("Configures bot kickers. \037option\037 can be one of:")); 133 134 Anope::string this_name = source.command; 135 for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) 136 { 137 const Anope::string &c_name = it->first; 138 const CommandInfo &info = it->second; 139 140 if (c_name.find_ci(this_name + " ") == 0) 141 { 142 ServiceReference<Command> command("Command", info.name); 143 if (command) 144 { 145 source.command = c_name; 146 command->OnServHelp(source); 147 } 148 } 149 } 150 151 source.Reply(_("Type \002%s%s HELP %s \037option\037\002 for more information\n" 152 "on a specific option.\n" 153 " \n" 154 "Note: access to this command is controlled by the\n" 155 "level SET."), Config->StrictPrivmsg.c_str(), source.service->nick.c_str(), this_name.c_str()); 156 157 return true; 158 } 159 }; 160 161 class CommandBSKickBase : public Command 162 { 163 public: 164 CommandBSKickBase(Module *creator, const Anope::string &cname, int minarg, int maxarg) : Command(creator, cname, minarg, maxarg) 165 { 166 } 167 168 virtual void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override = 0; 169 170 virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override = 0; 171 172 protected: 173 bool CheckArguments(CommandSource &source, const std::vector<Anope::string> ¶ms, ChannelInfo* &ci) 174 { 175 const Anope::string &chan = params[0]; 176 const Anope::string &option = params[1]; 177 178 ci = ChannelInfo::Find(chan); 179 180 if (Anope::ReadOnly) 181 source.Reply(_("Sorry, kicker configuration is temporarily disabled.")); 182 else if (ci == NULL) 183 source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); 184 else if (option.empty()) 185 this->OnSyntaxError(source, ""); 186 else if (!option.equals_ci("ON") && !option.equals_ci("OFF")) 187 this->OnSyntaxError(source, ""); 188 else if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("botserv/administration")) 189 source.Reply(ACCESS_DENIED); 190 else if (!ci->bi) 191 source.Reply(BOT_NOT_ASSIGNED); 192 else 193 return true; 194 195 return false; 196 } 197 198 void Process(CommandSource &source, ChannelInfo *ci, const Anope::string ¶m, const Anope::string &ttb, size_t ttb_idx, const Anope::string &optname, KickerData *kd, bool &val) 199 { 200 if (param.equals_ci("ON")) 201 { 202 if (!ttb.empty()) 203 { 204 int16_t i; 205 206 try 207 { 208 i = convertTo<int16_t>(ttb); 209 if (i < 0) 210 throw ConvertException(); 211 } 212 catch (const ConvertException &) 213 { 214 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); 215 return; 216 } 217 218 kd->ttb[ttb_idx] = i; 219 } 220 else 221 kd->ttb[ttb_idx] = 0; 222 223 val = true; 224 if (kd->ttb[ttb_idx]) 225 source.Reply(_("Bot will now kick for \002%s\002, and will place a ban\n" 226 "after %d kicks for the same user."), optname.c_str(), kd->ttb[ttb_idx]); 227 else 228 source.Reply(_("Bot will now kick for \002%s\002."), optname.c_str()); 229 230 bool override = !source.AccessFor(ci).HasPriv("SET"); 231 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable the " << optname << " kicker"; 232 } 233 else if (param.equals_ci("OFF")) 234 { 235 bool override = !source.AccessFor(ci).HasPriv("SET"); 236 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable the " << optname << " kicker"; 237 238 val = false; 239 source.Reply(_("Bot won't kick for \002%s\002 anymore."), optname.c_str()); 240 } 241 else 242 this->OnSyntaxError(source, ""); 243 } 244 }; 245 246 class CommandBSKickAMSG : public CommandBSKickBase 247 { 248 public: 249 CommandBSKickAMSG(Module *creator) : CommandBSKickBase(creator, "botserv/kick/amsg", 2, 3) 250 { 251 this->SetDesc(_("Configures AMSG kicker")); 252 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); 253 } 254 255 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 256 { 257 ChannelInfo *ci; 258 if (CheckArguments(source, params, ci)) 259 { 260 KickerData *kd = ci->Require<KickerData>("kickerdata"); 261 Process(source, ci, params[1], params.size() > 2 ? params[2] : "", TTB_AMSGS, "AMSG", kd, kd->amsgs); 262 kd->Check(ci); 263 } 264 } 265 266 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 267 { 268 this->SendSyntax(source); 269 source.Reply(" "); 270 BotInfo *bi = Config->GetClient("BotServ"); 271 source.Reply(_("Sets the AMSG kicker on or off. When enabled, the bot will\n" 272 "kick users who send the same message to multiple channels\n" 273 "where %s bots are.\n" 274 " \n" 275 "\037ttb\037 is the number of times a user can be kicked\n" 276 "before they get banned. Don't give ttb to disable\n" 277 "the ban system once activated."), bi ? bi->nick.c_str() : "BotServ"); 278 return true; 279 } 280 }; 281 282 class CommandBSKickBadwords : public CommandBSKickBase 283 { 284 public: 285 CommandBSKickBadwords(Module *creator) : CommandBSKickBase(creator, "botserv/kick/badwords", 2, 3) 286 { 287 this->SetDesc(_("Configures badwords kicker")); 288 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); 289 } 290 291 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 292 { 293 ChannelInfo *ci; 294 if (CheckArguments(source, params, ci)) 295 { 296 KickerData *kd = ci->Require<KickerData>("kickerdata"); 297 Process(source, ci, params[1], params.size() > 2 ? params[2] : "", TTB_BADWORDS, "badwords", kd, kd->badwords); 298 kd->Check(ci); 299 } 300 301 } 302 303 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 304 { 305 this->SendSyntax(source); 306 source.Reply(" "); 307 source.Reply(_("Sets the bad words kicker on or off. When enabled, this\n" 308 "option tells the bot to kick users who say certain words\n" 309 "on the channels.\n" 310 "You can define bad words for your channel using the\n" 311 "\002BADWORDS\002 command. Type \002%s%s HELP BADWORDS\002 for\n" 312 "more information.\n" 313 " \n" 314 "\037ttb\037 is the number of times a user can be kicked\n" 315 "before it gets banned. Don't give ttb to disable\n" 316 "the ban system once activated."), Config->StrictPrivmsg.c_str(), source.service->nick.c_str()); 317 return true; 318 } 319 }; 320 321 class CommandBSKickBolds : public CommandBSKickBase 322 { 323 public: 324 CommandBSKickBolds(Module *creator) : CommandBSKickBase(creator, "botserv/kick/bolds", 2, 3) 325 { 326 this->SetDesc(_("Configures bolds kicker")); 327 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); 328 } 329 330 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 331 { 332 ChannelInfo *ci; 333 if (CheckArguments(source, params, ci)) 334 { 335 KickerData *kd = ci->Require<KickerData>("kickerdata"); 336 Process(source, ci, params[1], params.size() > 2 ? params[2] : "", TTB_BOLDS, "bolds", kd, kd->bolds); 337 kd->Check(ci); 338 } 339 } 340 341 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 342 { 343 this->SendSyntax(source); 344 source.Reply(" "); 345 source.Reply(_("Sets the bolds kicker on or off. When enabled, this\n" 346 "option tells the bot to kick users who use bolds.\n" 347 " \n" 348 "\037ttb\037 is the number of times a user can be kicked\n" 349 "before it gets banned. Don't give ttb to disable\n" 350 "the ban system once activated.")); 351 return true; 352 } 353 }; 354 355 class CommandBSKickCaps : public CommandBSKickBase 356 { 357 public: 358 CommandBSKickCaps(Module *creator) : CommandBSKickBase(creator, "botserv/kick/caps", 2, 5) 359 { 360 this->SetDesc(_("Configures caps kicker")); 361 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037min\037 [\037percent\037]]]\002")); 362 } 363 364 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 365 { 366 ChannelInfo *ci; 367 if (!CheckArguments(source, params, ci)) 368 return; 369 370 KickerData *kd = ci->Require<KickerData>("kickerdata"); 371 372 if (params[1].equals_ci("ON")) 373 { 374 const Anope::string &ttb = params.size() > 2 ? params[2] : "", 375 &min = params.size() > 3 ? params[3] : "", 376 &percent = params.size() > 4 ? params[4] : ""; 377 378 if (!ttb.empty()) 379 { 380 try 381 { 382 kd->ttb[TTB_CAPS] = convertTo<int16_t>(ttb); 383 if (kd->ttb[TTB_CAPS] < 0) 384 throw ConvertException(); 385 } 386 catch (const ConvertException &) 387 { 388 kd->ttb[TTB_CAPS] = 0; 389 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); 390 return; 391 } 392 } 393 else 394 kd->ttb[TTB_CAPS] = 0; 395 396 kd->capsmin = 10; 397 try 398 { 399 kd->capsmin = convertTo<int16_t>(min); 400 } 401 catch (const ConvertException &) { } 402 if (kd->capsmin < 1) 403 kd->capsmin = 10; 404 405 kd->capspercent = 25; 406 try 407 { 408 kd->capspercent = convertTo<int16_t>(percent); 409 } 410 catch (const ConvertException &) { } 411 if (kd->capspercent < 1 || kd->capspercent > 100) 412 kd->capspercent = 25; 413 414 kd->caps = true; 415 if (kd->ttb[TTB_CAPS]) 416 source.Reply(_("Bot will now kick for \002caps\002 (they must constitute at least\n" 417 "%d characters and %d%% of the entire message), and will\n" 418 "place a ban after %d kicks for the same user."), kd->capsmin, kd->capspercent, kd->ttb[TTB_CAPS]); 419 else 420 source.Reply(_("Bot will now kick for \002caps\002 (they must constitute at least\n" 421 "%d characters and %d%% of the entire message)."), kd->capsmin, kd->capspercent); 422 } 423 else 424 { 425 kd->caps = false; 426 source.Reply(_("Bot won't kick for \002caps\002 anymore.")); 427 } 428 429 kd->Check(ci); 430 } 431 432 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 433 { 434 this->SendSyntax(source); 435 source.Reply(" "); 436 source.Reply(_("Sets the caps kicker on or off. When enabled, this\n" 437 "option tells the bot to kick users who are talking in\n" 438 "CAPS.\n" 439 "The bot kicks only if there are at least \002min\002 caps\n" 440 "and they constitute at least \002percent\002%% of the total\n" 441 "text line (if not given, it defaults to 10 characters\n" 442 "and 25%%).\n" 443 " \n" 444 "\037ttb\037 is the number of times a user can be kicked\n" 445 "before it gets banned. Don't give ttb to disable\n" 446 "the ban system once activated.")); 447 return true; 448 } 449 }; 450 451 class CommandBSKickColors : public CommandBSKickBase 452 { 453 public: 454 CommandBSKickColors(Module *creator) : CommandBSKickBase(creator, "botserv/kick/colors", 2, 3) 455 { 456 this->SetDesc(_("Configures color kicker")); 457 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); 458 } 459 460 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 461 { 462 ChannelInfo *ci; 463 if (CheckArguments(source, params, ci)) 464 { 465 KickerData *kd = ci->Require<KickerData>("kickerdata"); 466 Process(source, ci, params[1], params.size() > 2 ? params[2] : "", TTB_COLORS, "colors", kd, kd->colors); 467 kd->Check(ci); 468 } 469 } 470 471 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 472 { 473 this->SendSyntax(source); 474 source.Reply(" "); 475 source.Reply(_("Sets the colors kicker on or off. When enabled, this\n" 476 "option tells the bot to kick users who use colors.\n" 477 " \n" 478 "\037ttb\037 is the number of times a user can be kicked\n" 479 "before it gets banned. Don't give ttb to disable\n" 480 "the ban system once activated.")); 481 return true; 482 } 483 }; 484 485 class CommandBSKickFlood : public CommandBSKickBase 486 { 487 public: 488 CommandBSKickFlood(Module *creator) : CommandBSKickBase(creator, "botserv/kick/flood", 2, 5) 489 { 490 this->SetDesc(_("Configures flood kicker")); 491 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037ln\037 [\037secs\037]]]\002")); 492 } 493 494 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 495 { 496 ChannelInfo *ci; 497 if (!CheckArguments(source, params, ci)) 498 return; 499 500 KickerData *kd = ci->Require<KickerData>("kickerdata"); 501 502 if (params[1].equals_ci("ON")) 503 { 504 const Anope::string &ttb = params.size() > 2 ? params[2] : "", 505 &lines = params.size() > 3 ? params[3] : "", 506 &secs = params.size() > 4 ? params[4] : ""; 507 508 if (!ttb.empty()) 509 { 510 int16_t i; 511 512 try 513 { 514 i = convertTo<int16_t>(ttb); 515 if (i < 0) 516 throw ConvertException(); 517 } 518 catch (const ConvertException &) 519 { 520 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); 521 return; 522 } 523 524 kd->ttb[TTB_FLOOD] = i; 525 } 526 else 527 kd->ttb[TTB_FLOOD] = 0; 528 529 kd->floodlines = 6; 530 try 531 { 532 kd->floodlines = convertTo<int16_t>(lines); 533 } 534 catch (const ConvertException &) { } 535 if (kd->floodlines < 2) 536 kd->floodlines = 6; 537 538 kd->floodsecs = 10; 539 try 540 { 541 kd->floodsecs = convertTo<int16_t>(secs); 542 } 543 catch (const ConvertException &) { } 544 if (kd->floodsecs < 1) 545 kd->floodsecs = 10; 546 if (kd->floodsecs > Config->GetModule(me)->Get<time_t>("keepdata")) 547 kd->floodsecs = Config->GetModule(me)->Get<time_t>("keepdata"); 548 549 kd->flood = true; 550 if (kd->ttb[TTB_FLOOD]) 551 source.Reply(_("Bot will now kick for \002flood\002 (%d lines in %d seconds\n" 552 "and will place a ban after %d kicks for the same user."), kd->floodlines, kd->floodsecs, kd->ttb[TTB_FLOOD]); 553 else 554 source.Reply(_("Bot will now kick for \002flood\002 (%d lines in %d seconds)."), kd->floodlines, kd->floodsecs); 555 } 556 else if (params[1].equals_ci("OFF")) 557 { 558 kd->flood = false; 559 source.Reply(_("Bot won't kick for \002flood\002 anymore.")); 560 } 561 else 562 this->OnSyntaxError(source, params[1]); 563 564 kd->Check(ci); 565 } 566 567 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 568 { 569 this->SendSyntax(source); 570 source.Reply(" "); 571 source.Reply(_("Sets the flood kicker on or off. When enabled, this\n" 572 "option tells the bot to kick users who are flooding\n" 573 "the channel using at least \002ln\002 lines in \002secs\002 seconds\n" 574 "(if not given, it defaults to 6 lines in 10 seconds).\n" 575 " \n" 576 "\037ttb\037 is the number of times a user can be kicked\n" 577 "before it gets banned. Don't give ttb to disable\n" 578 "the ban system once activated.")); 579 return true; 580 } 581 }; 582 583 class CommandBSKickItalics : public CommandBSKickBase 584 { 585 public: 586 CommandBSKickItalics(Module *creator) : CommandBSKickBase(creator, "botserv/kick/italics", 2, 3) 587 { 588 this->SetDesc(_("Configures italics kicker")); 589 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); 590 } 591 592 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 593 { 594 ChannelInfo *ci; 595 if (CheckArguments(source, params, ci)) 596 { 597 KickerData *kd = ci->Require<KickerData>("kickerdata"); 598 Process(source, ci, params[1], params.size() > 2 ? params[2] : "", TTB_ITALICS, "italics", kd, kd->italics); 599 kd->Check(ci); 600 } 601 } 602 603 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 604 { 605 this->SendSyntax(source); 606 source.Reply(" "); 607 source.Reply(_("Sets the italics kicker on or off. When enabled, this\n" 608 "option tells the bot to kick users who use italics.\n" 609 " \n" 610 "\037ttb\037 is the number of times a user can be kicked\n" 611 "before it gets banned. Don't give ttb to disable\n" 612 "the ban system once activated.")); 613 return true; 614 } 615 }; 616 617 class CommandBSKickRepeat : public CommandBSKickBase 618 { 619 public: 620 CommandBSKickRepeat(Module *creator) : CommandBSKickBase(creator, "botserv/kick/repeat", 2, 4) 621 { 622 this->SetDesc(_("Configures repeat kicker")); 623 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037num\037]]\002")); 624 } 625 626 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 627 { 628 ChannelInfo *ci; 629 if (!CheckArguments(source, params, ci)) 630 return; 631 632 KickerData *kd = ci->Require<KickerData>("kickerdata"); 633 634 if (params[1].equals_ci("ON")) 635 { 636 const Anope::string &ttb = params.size() > 2 ? params[2] : "", 637 × = params.size() > 3 ? params[3] : ""; 638 639 if (!ttb.empty()) 640 { 641 int16_t i; 642 643 try 644 { 645 i = convertTo<int16_t>(ttb); 646 if (i < 0) 647 throw ConvertException(); 648 } 649 catch (const ConvertException &) 650 { 651 source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); 652 return; 653 } 654 655 kd->ttb[TTB_REPEAT] = i; 656 } 657 else 658 kd->ttb[TTB_REPEAT] = 0; 659 660 kd->repeattimes = 3; 661 try 662 { 663 kd->repeattimes = convertTo<int16_t>(times); 664 } 665 catch (const ConvertException &) { } 666 if (kd->repeattimes < 1) 667 kd->repeattimes = 3; 668 669 kd->repeat = true; 670 if (kd->ttb[TTB_REPEAT]) 671 { 672 if (kd->repeattimes != 1) 673 source.Reply(_("Bot will now kick for \002repeats\002 (users that repeat the\n" 674 "same message %d times), and will place a ban after %d\n" 675 "kicks for the same user."), kd->repeattimes, kd->ttb[TTB_REPEAT]); 676 else 677 source.Reply(_("Bot will now kick for \002repeats\002 (users that repeat the\n" 678 "same message %d time), and will place a ban after %d\n" 679 "kicks for the same user."), kd->repeattimes, kd->ttb[TTB_REPEAT]); 680 } 681 else 682 { 683 if (kd->repeattimes != 1) 684 source.Reply(_("Bot will now kick for \002repeats\002 (users that repeat the\n" 685 "same message %d times)."), kd->repeattimes); 686 else 687 source.Reply(_("Bot will now kick for \002repeats\002 (users that repeat the\n" 688 "same message %d time)."), kd->repeattimes); 689 } 690 } 691 else if (params[1].equals_ci("OFF")) 692 { 693 kd->repeat = false; 694 source.Reply(_("Bot won't kick for \002repeats\002 anymore.")); 695 } 696 else 697 this->OnSyntaxError(source, params[1]); 698 699 kd->Check(ci); 700 } 701 702 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 703 { 704 this->SendSyntax(source); 705 source.Reply(" "); 706 source.Reply(_("Sets the repeat kicker on or off. When enabled, this\n" 707 "option tells the bot to kick users who are repeating\n" 708 "themselves \002num\002 times (if num is not given, it\n" 709 "defaults to 3).\n" 710 " \n" 711 "\037ttb\037 is the number of times a user can be kicked\n" 712 "before it gets banned. Don't give ttb to disable\n" 713 "the ban system once activated.")); 714 return true; 715 } 716 }; 717 718 class CommandBSKickReverses : public CommandBSKickBase 719 { 720 public: 721 CommandBSKickReverses(Module *creator) : CommandBSKickBase(creator, "botserv/kick/reverses", 2, 3) 722 { 723 this->SetDesc(_("Configures reverses kicker")); 724 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); 725 } 726 727 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 728 { 729 ChannelInfo *ci; 730 if (CheckArguments(source, params, ci)) 731 { 732 KickerData *kd = ci->Require<KickerData>("kickerdata"); 733 Process(source, ci, params[1], params.size() > 2 ? params[2] : "", TTB_REVERSES, "reverses", kd, kd->reverses); 734 kd->Check(ci); 735 } 736 } 737 738 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 739 { 740 this->SendSyntax(source); 741 source.Reply(" "); 742 source.Reply(_("Sets the reverses kicker on or off. When enabled, this\n" 743 "option tells the bot to kick users who use reverses.\n" 744 " \n" 745 "\037ttb\037 is the number of times a user can be kicked\n" 746 "before it gets banned. Don't give ttb to disable\n" 747 "the ban system once activated.")); 748 return true; 749 } 750 }; 751 752 class CommandBSKickUnderlines : public CommandBSKickBase 753 { 754 public: 755 CommandBSKickUnderlines(Module *creator) : CommandBSKickBase(creator, "botserv/kick/underlines", 2, 3) 756 { 757 this->SetDesc(_("Configures underlines kicker")); 758 this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); 759 } 760 761 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 762 { 763 ChannelInfo *ci; 764 if (CheckArguments(source, params, ci)) 765 { 766 KickerData *kd = ci->Require<KickerData>("kickerdata"); 767 Process(source, ci, params[1], params.size() > 2 ? params[2] : "", TTB_UNDERLINES, "underlines", kd, kd->underlines); 768 kd->Check(ci); 769 } 770 } 771 772 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 773 { 774 this->SendSyntax(source); 775 source.Reply(" "); 776 source.Reply(_("Sets the underlines kicker on or off. When enabled, this\n" 777 "option tells the bot to kick users who use underlines.\n" 778 " \n" 779 "\037ttb\037 is the number of times a user can be kicked\n" 780 "before it gets banned. Don't give ttb to disable\n" 781 "the ban system once activated.")); 782 return true; 783 } 784 }; 785 786 class CommandBSSetDontKickOps : public Command 787 { 788 public: 789 CommandBSSetDontKickOps(Module *creator, const Anope::string &sname = "botserv/set/dontkickops") : Command(creator, sname, 2, 2) 790 { 791 this->SetDesc(_("To protect ops against bot kicks")); 792 this->SetSyntax(_("\037channel\037 {ON | OFF}")); 793 } 794 795 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 796 { 797 ChannelInfo *ci = ChannelInfo::Find(params[0]); 798 if (ci == NULL) 799 { 800 source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); 801 return; 802 } 803 804 AccessGroup access = source.AccessFor(ci); 805 if (!source.HasPriv("botserv/administration") && !access.HasPriv("SET")) 806 { 807 source.Reply(ACCESS_DENIED); 808 return; 809 } 810 811 if (Anope::ReadOnly) 812 { 813 source.Reply(_("Sorry, bot option setting is temporarily disabled.")); 814 return; 815 } 816 817 KickerData *kd = ci->Require<KickerData>("kickerdata"); 818 if (params[1].equals_ci("ON")) 819 { 820 bool override = !access.HasPriv("SET"); 821 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable dontkickops"; 822 823 kd->dontkickops = true; 824 source.Reply(_("Bot \002won't kick ops\002 on channel %s."), ci->name.c_str()); 825 } 826 else if (params[1].equals_ci("OFF")) 827 { 828 bool override = !access.HasPriv("SET"); 829 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable dontkickops"; 830 831 kd->dontkickops = false; 832 source.Reply(_("Bot \002will kick ops\002 on channel %s."), ci->name.c_str()); 833 } 834 else 835 this->OnSyntaxError(source, source.command); 836 837 kd->Check(ci); 838 } 839 840 bool OnHelp(CommandSource &source, const Anope::string &) anope_override 841 { 842 this->SendSyntax(source); 843 source.Reply(_(" \n" 844 "Enables or disables \002ops protection\002 mode on a channel.\n" 845 "When it is enabled, ops won't be kicked by the bot\n" 846 "even if they don't match the NOKICK level.")); 847 return true; 848 } 849 }; 850 851 class CommandBSSetDontKickVoices : public Command 852 { 853 public: 854 CommandBSSetDontKickVoices(Module *creator, const Anope::string &sname = "botserv/set/dontkickvoices") : Command(creator, sname, 2, 2) 855 { 856 this->SetDesc(_("To protect voices against bot kicks")); 857 this->SetSyntax(_("\037channel\037 {ON | OFF}")); 858 } 859 860 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 861 { 862 ChannelInfo *ci = ChannelInfo::Find(params[0]); 863 if (ci == NULL) 864 { 865 source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); 866 return; 867 } 868 869 AccessGroup access = source.AccessFor(ci); 870 if (!source.HasPriv("botserv/administration") && !access.HasPriv("SET")) 871 { 872 source.Reply(ACCESS_DENIED); 873 return; 874 } 875 876 if (Anope::ReadOnly) 877 { 878 source.Reply(_("Sorry, bot option setting is temporarily disabled.")); 879 return; 880 } 881 882 KickerData *kd = ci->Require<KickerData>("kickerdata"); 883 if (params[1].equals_ci("ON")) 884 { 885 bool override = !access.HasPriv("SET"); 886 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable dontkickvoices"; 887 888 kd->dontkickvoices = true; 889 source.Reply(_("Bot \002won't kick voices\002 on channel %s."), ci->name.c_str()); 890 } 891 else if (params[1].equals_ci("OFF")) 892 { 893 bool override = !access.HasPriv("SET"); 894 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable dontkickvoices"; 895 896 kd->dontkickvoices = false; 897 source.Reply(_("Bot \002will kick voices\002 on channel %s."), ci->name.c_str()); 898 } 899 else 900 this->OnSyntaxError(source, source.command); 901 902 kd->Check(ci); 903 } 904 905 bool OnHelp(CommandSource &source, const Anope::string &) anope_override 906 { 907 this->SendSyntax(source); 908 source.Reply(_(" \n" 909 "Enables or disables \002voices protection\002 mode on a channel.\n" 910 "When it is enabled, voices won't be kicked by the bot\n" 911 "even if they don't match the NOKICK level.")); 912 return true; 913 } 914 }; 915 916 struct BanData 917 { 918 struct Data 919 { 920 Anope::string mask; 921 time_t last_use; 922 int16_t ttb[TTB_SIZE]; 923 924 Data() 925 { 926 last_use = 0; 927 for (int i = 0; i < TTB_SIZE; ++i) 928 this->ttb[i] = 0; 929 } 930 }; 931 932 private: 933 typedef Anope::map<Data> data_type; 934 data_type data_map; 935 936 public: 937 BanData(Extensible *) { } 938 939 Data &get(const Anope::string &key) 940 { 941 return this->data_map[key]; 942 } 943 944 bool empty() const 945 { 946 return this->data_map.empty(); 947 } 948 949 void purge() 950 { 951 time_t keepdata = Config->GetModule(me)->Get<time_t>("keepdata"); 952 for (data_type::iterator it = data_map.begin(), it_end = data_map.end(); it != it_end;) 953 { 954 const Anope::string &user = it->first; 955 Data &bd = it->second; 956 ++it; 957 958 if (Anope::CurTime - bd.last_use > keepdata) 959 data_map.erase(user); 960 } 961 } 962 }; 963 964 struct UserData 965 { 966 UserData(Extensible *) 967 { 968 last_use = last_start = Anope::CurTime; 969 lines = times = 0; 970 lastline.clear(); 971 } 972 973 /* Data validity */ 974 time_t last_use; 975 976 /* for flood kicker */ 977 int16_t lines; 978 time_t last_start; 979 980 /* for repeat kicker */ 981 Anope::string lasttarget; 982 int16_t times; 983 984 Anope::string lastline; 985 }; 986 987 class BanDataPurger : public Timer 988 { 989 public: 990 BanDataPurger(Module *o) : Timer(o, 300, Anope::CurTime, true) { } 991 992 void Tick(time_t) anope_override 993 { 994 Log(LOG_DEBUG) << "bs_main: Running bandata purger"; 995 996 for (channel_map::iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) 997 { 998 Channel *c = it->second; 999 1000 BanData *bd = c->GetExt<BanData>("bandata"); 1001 if (bd != NULL) 1002 { 1003 bd->purge(); 1004 if (bd->empty()) 1005 c->Shrink<BanData>("bandata"); 1006 } 1007 } 1008 } 1009 }; 1010 1011 class BSKick : public Module 1012 { 1013 ExtensibleItem<BanData> bandata; 1014 ExtensibleItem<UserData> userdata; 1015 KickerDataImpl::ExtensibleItem kickerdata; 1016 1017 CommandBSKick commandbskick; 1018 CommandBSKickAMSG commandbskickamsg; 1019 CommandBSKickBadwords commandbskickbadwords; 1020 CommandBSKickBolds commandbskickbolds; 1021 CommandBSKickCaps commandbskickcaps; 1022 CommandBSKickColors commandbskickcolors; 1023 CommandBSKickFlood commandbskickflood; 1024 CommandBSKickItalics commandbskickitalics; 1025 CommandBSKickRepeat commandbskickrepeat; 1026 CommandBSKickReverses commandbskickreverse; 1027 CommandBSKickUnderlines commandbskickunderlines; 1028 1029 CommandBSSetDontKickOps commandbssetdontkickops; 1030 CommandBSSetDontKickVoices commandbssetdontkickvoices; 1031 1032 BanDataPurger purger; 1033 1034 BanData::Data &GetBanData(User *u, Channel *c) 1035 { 1036 BanData *bd = bandata.Require(c); 1037 return bd->get(u->GetMask()); 1038 } 1039 1040 UserData *GetUserData(User *u, Channel *c) 1041 { 1042 ChanUserContainer *uc = c->FindUser(u); 1043 if (uc == NULL) 1044 return NULL; 1045 1046 UserData *ud = userdata.Require(uc); 1047 return ud; 1048 } 1049 1050 void check_ban(ChannelInfo *ci, User *u, KickerData *kd, int ttbtype) 1051 { 1052 /* Don't ban ulines or protected users */ 1053 if (u->IsProtected()) 1054 return; 1055 1056 BanData::Data &bd = this->GetBanData(u, ci->c); 1057 1058 ++bd.ttb[ttbtype]; 1059 if (kd->ttb[ttbtype] && bd.ttb[ttbtype] >= kd->ttb[ttbtype]) 1060 { 1061 /* Should not use == here because bd.ttb[ttbtype] could possibly be > kd->ttb[ttbtype] 1062 * if the TTB was changed after it was not set (0) before and the user had already been 1063 * kicked a few times. Bug #1056 - Adam */ 1064 1065 bd.ttb[ttbtype] = 0; 1066 1067 Anope::string mask = ci->GetIdealBan(u); 1068 1069 ci->c->SetMode(NULL, "BAN", mask); 1070 FOREACH_MOD(OnBotBan, (u, ci, mask)); 1071 } 1072 } 1073 1074 void bot_kick(ChannelInfo *ci, User *u, const char *message, ...) 1075 { 1076 va_list args; 1077 char buf[1024]; 1078 1079 if (!ci || !ci->bi || !ci->c || !u || u->IsProtected() || !ci->c->FindUser(u)) 1080 return; 1081 1082 Anope::string fmt = Language::Translate(u, message); 1083 va_start(args, message); 1084 vsnprintf(buf, sizeof(buf), fmt.c_str(), args); 1085 va_end(args); 1086 1087 ci->c->Kick(ci->bi, u, "%s", buf); 1088 } 1089 1090 public: 1091 BSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), 1092 bandata(this, "bandata"), 1093 userdata(this, "userdata"), 1094 kickerdata(this, "kickerdata"), 1095 1096 commandbskick(this), 1097 commandbskickamsg(this), commandbskickbadwords(this), commandbskickbolds(this), commandbskickcaps(this), 1098 commandbskickcolors(this), commandbskickflood(this), commandbskickitalics(this), commandbskickrepeat(this), 1099 commandbskickreverse(this), commandbskickunderlines(this), 1100 1101 commandbssetdontkickops(this), commandbssetdontkickvoices(this), 1102 1103 purger(this) 1104 { 1105 me = this; 1106 1107 } 1108 1109 void OnBotInfo(CommandSource &source, BotInfo *bi, ChannelInfo *ci, InfoFormatter &info) anope_override 1110 { 1111 if (!ci) 1112 return; 1113 1114 Anope::string enabled = Language::Translate(source.nc, _("Enabled")); 1115 Anope::string disabled = Language::Translate(source.nc, _("Disabled")); 1116 KickerData *kd = kickerdata.Get(ci); 1117 1118 if (kd && kd->badwords) 1119 { 1120 if (kd->ttb[TTB_BADWORDS]) 1121 info[_("Bad words kicker")] = Anope::printf("%s (%d kick(s) to ban)", enabled.c_str(), kd->ttb[TTB_BADWORDS]); 1122 else 1123 info[_("Bad words kicker")] = enabled; 1124 } 1125 else 1126 info[_("Bad words kicker")] = disabled; 1127 1128 if (kd && kd->bolds) 1129 { 1130 if (kd->ttb[TTB_BOLDS]) 1131 info[_("Bolds kicker")] = Anope::printf("%s (%d kick(s) to ban)", enabled.c_str(), kd->ttb[TTB_BOLDS]); 1132 else 1133 info[_("Bolds kicker")] = enabled; 1134 } 1135 else 1136 info[_("Bolds kicker")] = disabled; 1137 1138 if (kd && kd->caps) 1139 { 1140 if (kd->ttb[TTB_CAPS]) 1141 info[_("Caps kicker")] = Anope::printf(_("%s (%d kick(s) to ban; minimum %d/%d%%)"), enabled.c_str(), kd->ttb[TTB_CAPS], kd->capsmin, kd->capspercent); 1142 else 1143 info[_("Caps kicker")] = Anope::printf(_("%s (minimum %d/%d%%)"), enabled.c_str(), kd->capsmin, kd->capspercent); 1144 } 1145 else 1146 info[_("Caps kicker")] = disabled; 1147 1148 if (kd && kd->colors) 1149 { 1150 if (kd->ttb[TTB_COLORS]) 1151 info[_("Colors kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->ttb[TTB_COLORS]); 1152 else 1153 info[_("Colors kicker")] = enabled; 1154 } 1155 else 1156 info[_("Colors kicker")] = disabled; 1157 1158 if (kd && kd->flood) 1159 { 1160 if (kd->ttb[TTB_FLOOD]) 1161 info[_("Flood kicker")] = Anope::printf(_("%s (%d kick(s) to ban; %d lines in %ds)"), enabled.c_str(), kd->ttb[TTB_FLOOD], kd->floodlines, kd->floodsecs); 1162 else 1163 info[_("Flood kicker")] = Anope::printf(_("%s (%d lines in %ds)"), enabled.c_str(), kd->floodlines, kd->floodsecs); 1164 } 1165 else 1166 info[_("Flood kicker")] = disabled; 1167 1168 if (kd && kd->repeat) 1169 { 1170 if (kd->ttb[TTB_REPEAT]) 1171 info[_("Repeat kicker")] = Anope::printf(_("%s (%d kick(s) to ban; %d times)"), enabled.c_str(), kd->ttb[TTB_REPEAT], kd->repeattimes); 1172 else 1173 info[_("Repeat kicker")] = Anope::printf(_("%s (%d times)"), enabled.c_str(), kd->repeattimes); 1174 } 1175 else 1176 info[_("Repeat kicker")] = disabled; 1177 1178 if (kd && kd->reverses) 1179 { 1180 if (kd->ttb[TTB_REVERSES]) 1181 info[_("Reverses kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->ttb[TTB_REVERSES]); 1182 else 1183 info[_("Reverses kicker")] = enabled; 1184 } 1185 else 1186 info[_("Reverses kicker")] = disabled; 1187 1188 if (kd && kd->underlines) 1189 { 1190 if (kd->ttb[TTB_UNDERLINES]) 1191 info[_("Underlines kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->ttb[TTB_UNDERLINES]); 1192 else 1193 info[_("Underlines kicker")] = enabled; 1194 } 1195 else 1196 info[_("Underlines kicker")] = disabled; 1197 1198 if (kd && kd->italics) 1199 { 1200 if (kd->ttb[TTB_ITALICS]) 1201 info[_("Italics kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->ttb[TTB_ITALICS]); 1202 else 1203 info[_("Italics kicker")] = enabled; 1204 } 1205 else 1206 info[_("Italics kicker")] = disabled; 1207 1208 if (kd && kd->amsgs) 1209 { 1210 if (kd->ttb[TTB_AMSGS]) 1211 info[_("AMSG kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->ttb[TTB_AMSGS]); 1212 else 1213 info[_("AMSG kicker")] = enabled; 1214 } 1215 else 1216 info[_("AMSG kicker")] = disabled; 1217 1218 if (kd && kd->dontkickops) 1219 info.AddOption(_("Ops protection")); 1220 if (kd && kd->dontkickvoices) 1221 info.AddOption(_("Voices protection")); 1222 } 1223 1224 void OnPrivmsg(User *u, Channel *c, Anope::string &msg) anope_override 1225 { 1226 /* Now we can make kicker stuff. We try to order the checks 1227 * from the fastest one to the slowest one, since there's 1228 * no need to process other kickers if a user is kicked before 1229 * the last kicker check. 1230 * 1231 * But FIRST we check whether the user is protected in any 1232 * way. 1233 */ 1234 ChannelInfo *ci = c->ci; 1235 if (ci == NULL) 1236 return; 1237 KickerData *kd = kickerdata.Get(ci); 1238 if (kd == NULL) 1239 return; 1240 1241 if (ci->AccessFor(u).HasPriv("NOKICK")) 1242 return; 1243 else if (kd->dontkickops && (c->HasUserStatus(u, "HALFOP") || c->HasUserStatus(u, "OP") || c->HasUserStatus(u, "PROTECT") || c->HasUserStatus(u, "OWNER"))) 1244 return; 1245 else if (kd->dontkickvoices && c->HasUserStatus(u, "VOICE")) 1246 return; 1247 1248 Anope::string realbuf = msg; 1249 1250 /* If it's a /me, cut the CTCP part because the ACTION will cause 1251 * problems with the caps or badwords kicker 1252 */ 1253 if (realbuf.substr(0, 8).equals_ci("\1ACTION ") && realbuf[realbuf.length() - 1] == '\1') 1254 { 1255 realbuf.erase(0, 8); 1256 realbuf.erase(realbuf.length() - 1); 1257 } 1258 1259 if (realbuf.empty()) 1260 return; 1261 1262 /* Bolds kicker */ 1263 if (kd->bolds && realbuf.find(2) != Anope::string::npos) 1264 { 1265 check_ban(ci, u, kd, TTB_BOLDS); 1266 bot_kick(ci, u, _("Don't use bolds on this channel!")); 1267 return; 1268 } 1269 1270 /* Color kicker */ 1271 if (kd->colors && realbuf.find(3) != Anope::string::npos) 1272 { 1273 check_ban(ci, u, kd, TTB_COLORS); 1274 bot_kick(ci, u, _("Don't use colors on this channel!")); 1275 return; 1276 } 1277 1278 /* Reverses kicker */ 1279 if (kd->reverses && realbuf.find(22) != Anope::string::npos) 1280 { 1281 check_ban(ci, u, kd, TTB_REVERSES); 1282 bot_kick(ci, u, _("Don't use reverses on this channel!")); 1283 return; 1284 } 1285 1286 /* Italics kicker */ 1287 if (kd->italics && realbuf.find(29) != Anope::string::npos) 1288 { 1289 check_ban(ci, u, kd, TTB_ITALICS); 1290 bot_kick(ci, u, _("Don't use italics on this channel!")); 1291 return; 1292 } 1293 1294 /* Underlines kicker */ 1295 if (kd->underlines && realbuf.find(31) != Anope::string::npos) 1296 { 1297 check_ban(ci, u, kd, TTB_UNDERLINES); 1298 bot_kick(ci, u, _("Don't use underlines on this channel!")); 1299 return; 1300 } 1301 1302 /* Caps kicker */ 1303 if (kd->caps && realbuf.length() >= static_cast<unsigned>(kd->capsmin)) 1304 { 1305 int i = 0, l = 0; 1306 1307 for (unsigned j = 0, end = realbuf.length(); j < end; ++j) 1308 { 1309 if (isupper(realbuf[j])) 1310 ++i; 1311 else if (islower(realbuf[j])) 1312 ++l; 1313 } 1314 1315 /* i counts uppercase chars, l counts lowercase chars. Only 1316 * alphabetic chars (so islower || isupper) qualify for the 1317 * percentage of caps to kick for; the rest is ignored. -GD 1318 */ 1319 1320 if ((i || l) && i >= kd->capsmin && i * 100 / (i + l) >= kd->capspercent) 1321 { 1322 check_ban(ci, u, kd, TTB_CAPS); 1323 bot_kick(ci, u, _("Turn caps lock OFF!")); 1324 return; 1325 } 1326 } 1327 1328 /* Bad words kicker */ 1329 if (kd->badwords) 1330 { 1331 bool mustkick = false; 1332 BadWords *badwords = ci->GetExt<BadWords>("badwords"); 1333 1334 /* Normalize the buffer */ 1335 Anope::string nbuf = Anope::NormalizeBuffer(realbuf); 1336 bool casesensitive = Config->GetModule("botserv")->Get<bool>("casesensitive"); 1337 1338 /* Normalize can return an empty string if this only contains control codes etc */ 1339 if (badwords && !nbuf.empty()) 1340 for (unsigned i = 0; i < badwords->GetBadWordCount(); ++i) 1341 { 1342 const BadWord *bw = badwords->GetBadWord(i); 1343 1344 if (bw->word.empty()) 1345 continue; // Shouldn't happen 1346 1347 if (bw->word.length() > nbuf.length()) 1348 continue; // This can't ever match 1349 1350 if (bw->type == BW_ANY && ((casesensitive && nbuf.find(bw->word) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(bw->word) != Anope::string::npos))) 1351 mustkick = true; 1352 else if (bw->type == BW_SINGLE) 1353 { 1354 size_t len = bw->word.length(); 1355 1356 if ((casesensitive && bw->word.equals_cs(nbuf)) || (!casesensitive && bw->word.equals_ci(nbuf))) 1357 mustkick = true; 1358 else if (nbuf.find(' ') == len && ((casesensitive && bw->word.equals_cs(nbuf.substr(0, len))) || (!casesensitive && bw->word.equals_ci(nbuf.substr(0, len))))) 1359 mustkick = true; 1360 else 1361 { 1362 if (len < nbuf.length() && nbuf.rfind(' ') == nbuf.length() - len - 1 && ((casesensitive && nbuf.find(bw->word) == nbuf.length() - len) || (!casesensitive && nbuf.find_ci(bw->word) == nbuf.length() - len))) 1363 mustkick = true; 1364 else 1365 { 1366 Anope::string wordbuf = " " + bw->word + " "; 1367 1368 if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) 1369 mustkick = true; 1370 } 1371 } 1372 } 1373 else if (bw->type == BW_START) 1374 { 1375 size_t len = bw->word.length(); 1376 1377 if ((casesensitive && nbuf.substr(0, len).equals_cs(bw->word)) || (!casesensitive && nbuf.substr(0, len).equals_ci(bw->word))) 1378 mustkick = true; 1379 else 1380 { 1381 Anope::string wordbuf = " " + bw->word; 1382 1383 if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) 1384 mustkick = true; 1385 } 1386 } 1387 else if (bw->type == BW_END) 1388 { 1389 size_t len = bw->word.length(); 1390 1391 if ((casesensitive && nbuf.substr(nbuf.length() - len).equals_cs(bw->word)) || (!casesensitive && nbuf.substr(nbuf.length() - len).equals_ci(bw->word))) 1392 mustkick = true; 1393 else 1394 { 1395 Anope::string wordbuf = bw->word + " "; 1396 1397 if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) 1398 mustkick = true; 1399 } 1400 } 1401 1402 if (mustkick) 1403 { 1404 check_ban(ci, u, kd, TTB_BADWORDS); 1405 if (Config->GetModule(me)->Get<bool>("gentlebadwordreason")) 1406 bot_kick(ci, u, _("Watch your language!")); 1407 else 1408 bot_kick(ci, u, _("Don't use the word \"%s\" on this channel!"), bw->word.c_str()); 1409 1410 return; 1411 } 1412 } /* for */ 1413 } /* if badwords */ 1414 1415 UserData *ud = GetUserData(u, c); 1416 1417 if (ud) 1418 { 1419 /* Flood kicker */ 1420 if (kd->flood) 1421 { 1422 if (Anope::CurTime - ud->last_start > kd->floodsecs) 1423 { 1424 ud->last_start = Anope::CurTime; 1425 ud->lines = 0; 1426 } 1427 1428 ++ud->lines; 1429 if (ud->lines >= kd->floodlines) 1430 { 1431 check_ban(ci, u, kd, TTB_FLOOD); 1432 bot_kick(ci, u, _("Stop flooding!")); 1433 return; 1434 } 1435 } 1436 1437 /* Repeat kicker */ 1438 if (kd->repeat) 1439 { 1440 if (!ud->lastline.equals_ci(realbuf)) 1441 ud->times = 0; 1442 else 1443 ++ud->times; 1444 1445 if (ud->times >= kd->repeattimes) 1446 { 1447 check_ban(ci, u, kd, TTB_REPEAT); 1448 bot_kick(ci, u, _("Stop repeating yourself!")); 1449 return; 1450 } 1451 } 1452 1453 if (ud->lastline.equals_ci(realbuf) && !ud->lasttarget.empty() && !ud->lasttarget.equals_ci(ci->name)) 1454 { 1455 for (User::ChanUserList::iterator it = u->chans.begin(); it != u->chans.end();) 1456 { 1457 Channel *chan = it->second->chan; 1458 ++it; 1459 1460 if (chan->ci && kd->amsgs && !chan->ci->AccessFor(u).HasPriv("NOKICK")) 1461 { 1462 check_ban(chan->ci, u, kd, TTB_AMSGS); 1463 bot_kick(chan->ci, u, _("Don't use AMSGs!")); 1464 } 1465 } 1466 } 1467 1468 ud->lasttarget = ci->name; 1469 ud->lastline = realbuf; 1470 } 1471 } 1472 }; 1473 1474 MODULE_INIT(BSKick)