bs_kick.cpp

Go to the documentation of this file.
00001 /* BotServ core functions
00002  *
00003  * (C) 2003-2013 Anope Team
00004  * Contact us at team@anope.org
00005  *
00006  * Please read COPYING and README for further details.
00007  *
00008  * Based on the original code of Epona by Lara.
00009  * Based on the original code of Services by Andy Church.
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> &params) 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                                                         /* reset the value back to 0 - TSL */
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         /* Data validity */
00651         time_t last_use;
00652 
00653         /* for flood kicker */
00654         int16_t lines;
00655         time_t last_start;
00656 
00657         /* for repeat kicker */
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                 /* Don't ban ulines */
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                         /* Should not use == here because bd.ttb[ttbtype] could possibly be > ci->ttb[ttbtype]
00734                          * if the TTB was changed after it was not set (0) before and the user had already been
00735                          * kicked a few times. Bug #1056 - Adam */
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                 /* Now we can make kicker stuff. We try to order the checks
00785                  * from the fastest one to the slowest one, since there's
00786                  * no need to process other kickers if a user is kicked before
00787                  * the last kicker check.
00788                  *
00789                  * But FIRST we check whether the user is protected in any
00790                  * way.
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                 /* If it's a /me, cut the CTCP part because the ACTION will cause
00806                  * problems with the caps or badwords kicker
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                 /* Bolds kicker */
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                 /* Color kicker */
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                 /* Reverses kicker */
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                 /* Italics kicker */
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                 /* Underlines kicker */
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                 /* Caps kicker */
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                         /* i counts uppercase chars, l counts lowercase chars. Only
00871                          * alphabetic chars (so islower || isupper) qualify for the
00872                          * percentage of caps to kick for; the rest is ignored. -GD
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                 /* Bad words kicker */
00884                 if (ci->HasExt("BS_KICK_BADWORDS"))
00885                 {
00886                         bool mustkick = false;
00887 
00888                         /* Normalize the buffer */
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                         } /* for */
00958                 } /* if badwords */
00959 
00960                 UserData *ud = GetUserData(u, c);
00961 
00962                 if (ud)
00963                 {
00964                         /* Flood kicker */
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                         /* Repeat kicker */
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)