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> &params) 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> &params) 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> &params, 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 &param, 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> &params) 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> &params) 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> &params) 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> &params) 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> &params) 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> &params) 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> &params) 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> &params) 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 						&times = 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> &params) 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> &params) 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> &params) 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> &params) 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)