anope

- supernets anope source code & configuration
git clone git://git.acid.vegas/anope.git
Log | Files | Refs | Archive | README

bs_badwords.cpp (13389B)

      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_badwords.h"
     14 
     15 struct BadWordImpl : BadWord, Serializable
     16 {
     17 	BadWordImpl() : Serializable("BadWord") { }
     18 	~BadWordImpl();
     19 
     20 	void Serialize(Serialize::Data &data) const anope_override
     21 	{
     22 		data["ci"] << this->chan;
     23 		data["word"] << this->word;
     24 		data.SetType("type", Serialize::Data::DT_INT); data["type"] << this->type;
     25 	}
     26 
     27 	static Serializable* Unserialize(Serializable *obj, Serialize::Data &);
     28 };
     29 
     30 struct BadWordsImpl : BadWords
     31 {
     32 	Serialize::Reference<ChannelInfo> ci;
     33 	typedef std::vector<BadWordImpl *> list;
     34 	Serialize::Checker<list> badwords;
     35 
     36 	BadWordsImpl(Extensible *obj) : ci(anope_dynamic_static_cast<ChannelInfo *>(obj)), badwords("BadWord") { }
     37 
     38 	~BadWordsImpl();
     39 
     40 	BadWord* AddBadWord(const Anope::string &word, BadWordType type) anope_override
     41 	{
     42 		BadWordImpl *bw = new BadWordImpl();
     43 		bw->chan = ci->name;
     44 		bw->word = word;
     45 		bw->type = type;
     46 
     47 		this->badwords->push_back(bw);
     48 
     49 		FOREACH_MOD(OnBadWordAdd, (ci, bw));
     50 
     51 		return bw;
     52 	}
     53 
     54 	BadWord* GetBadWord(unsigned index) const anope_override
     55 	{
     56 		if (this->badwords->empty() || index >= this->badwords->size())
     57 			return NULL;
     58 
     59 		BadWordImpl *bw = (*this->badwords)[index];
     60 		bw->QueueUpdate();
     61 		return bw;
     62 	}
     63 
     64 	unsigned GetBadWordCount() const anope_override
     65 	{
     66 		return this->badwords->size();
     67 	}
     68 
     69 	void EraseBadWord(unsigned index) anope_override
     70 	{
     71 		if (this->badwords->empty() || index >= this->badwords->size())
     72 			return;
     73 
     74 		FOREACH_MOD(OnBadWordDel, (ci, (*this->badwords)[index]));
     75 
     76 		delete this->badwords->at(index);
     77 	}
     78 
     79 	void ClearBadWords() anope_override
     80 	{
     81 		while (!this->badwords->empty())
     82 			delete this->badwords->back();
     83 	}
     84 
     85 	void Check() anope_override
     86 	{
     87 		if (this->badwords->empty())
     88 			ci->Shrink<BadWords>("badwords");
     89 	}
     90 };
     91 
     92 BadWordsImpl::~BadWordsImpl()
     93 {
     94 	for (list::iterator it = badwords->begin(); it != badwords->end();)
     95 	{
     96 		BadWord *bw = *it;
     97 		++it;
     98 		delete bw;
     99 	}
    100 }
    101 
    102 BadWordImpl::~BadWordImpl()
    103 {
    104 	ChannelInfo *ci = ChannelInfo::Find(chan);
    105 	if (ci)
    106 	{
    107 		BadWordsImpl *badwords = ci->GetExt<BadWordsImpl>("badwords");
    108 		if (badwords)
    109 		{
    110 			BadWordsImpl::list::iterator it = std::find(badwords->badwords->begin(), badwords->badwords->end(), this);
    111 			if (it != badwords->badwords->end())
    112 				badwords->badwords->erase(it);
    113 		}
    114 	}
    115 }
    116 
    117 Serializable* BadWordImpl::Unserialize(Serializable *obj, Serialize::Data &data)
    118 {
    119 	Anope::string sci, sword;
    120 
    121 	data["ci"] >> sci;
    122 	data["word"] >> sword;
    123 
    124 	ChannelInfo *ci = ChannelInfo::Find(sci);
    125 	if (!ci)
    126 		return NULL;
    127 
    128 	unsigned int n;
    129 	data["type"] >> n;
    130 
    131 	BadWordImpl *bw;
    132 	if (obj)
    133 		bw = anope_dynamic_static_cast<BadWordImpl *>(obj);
    134 	else
    135 		bw = new BadWordImpl();
    136 	bw->chan = sci;
    137 	bw->word = sword;
    138 	bw->type = static_cast<BadWordType>(n);
    139 
    140 	BadWordsImpl *bws = ci->Require<BadWordsImpl>("badwords");
    141 	if (!obj)
    142 		bws->badwords->push_back(bw);
    143 
    144 	return bw;
    145 }
    146 
    147 class BadwordsDelCallback : public NumberList
    148 {
    149 	CommandSource &source;
    150 	ChannelInfo *ci;
    151 	BadWords *bw;
    152 	Command *c;
    153 	unsigned deleted;
    154 	bool override;
    155  public:
    156 	BadwordsDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, const Anope::string &list) : NumberList(list, true), source(_source), ci(_ci), c(_c), deleted(0), override(false)
    157 	{
    158 		if (!source.AccessFor(ci).HasPriv("BADWORDS") && source.HasPriv("botserv/administration"))
    159 			this->override = true;
    160 		bw = ci->Require<BadWords>("badwords");
    161 	}
    162 
    163 	~BadwordsDelCallback()
    164 	{
    165 		if (!deleted)
    166 			source.Reply(_("No matching entries on %s bad words list."), ci->name.c_str());
    167 		else if (deleted == 1)
    168 			source.Reply(_("Deleted 1 entry from %s bad words list."), ci->name.c_str());
    169 		else
    170 			source.Reply(_("Deleted %d entries from %s bad words list."), deleted, ci->name.c_str());
    171 	}
    172 
    173 	void HandleNumber(unsigned Number) anope_override
    174 	{
    175 		if (!bw || !Number || Number > bw->GetBadWordCount())
    176 			return;
    177 
    178 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, c, ci) << "DEL " << bw->GetBadWord(Number - 1)->word;
    179 		++deleted;
    180 		bw->EraseBadWord(Number - 1);
    181 	}
    182 };
    183 
    184 class CommandBSBadwords : public Command
    185 {
    186  private:
    187 	void DoList(CommandSource &source, ChannelInfo *ci, const Anope::string &word)
    188 	{
    189 		bool override = !source.AccessFor(ci).HasPriv("BADWORDS");
    190 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "LIST";
    191 		ListFormatter list(source.GetAccount());
    192 		BadWords *bw = ci->GetExt<BadWords>("badwords");
    193 
    194 		list.AddColumn(_("Number")).AddColumn(_("Word")).AddColumn(_("Type"));
    195 
    196 		if (!bw || !bw->GetBadWordCount())
    197 		{
    198 			source.Reply(_("%s bad words list is empty."), ci->name.c_str());
    199 			return;
    200 		}
    201 		else if (!word.empty() && word.find_first_not_of("1234567890,-") == Anope::string::npos)
    202 		{
    203 			class BadwordsListCallback : public NumberList
    204 			{
    205 				ListFormatter &list;
    206 				BadWords *bw;
    207 			 public:
    208 				BadwordsListCallback(ListFormatter &_list, BadWords *_bw, const Anope::string &numlist) : NumberList(numlist, false), list(_list), bw(_bw)
    209 				{
    210 				}
    211 
    212 				void HandleNumber(unsigned Number) anope_override
    213 				{
    214 					if (!Number || Number > bw->GetBadWordCount())
    215 						return;
    216 
    217 					const BadWord *b = bw->GetBadWord(Number - 1);
    218 					ListFormatter::ListEntry entry;
    219 					entry["Number"] = stringify(Number);
    220 					entry["Word"] = b->word;
    221 					entry["Type"] = b->type == BW_SINGLE ? "(SINGLE)" : (b->type == BW_START ? "(START)" : (b->type == BW_END ? "(END)" : ""));
    222 					this->list.AddEntry(entry);
    223 				}
    224 			}
    225 			nl_list(list, bw, word);
    226 			nl_list.Process();
    227 		}
    228 		else
    229 		{
    230 			for (unsigned i = 0, end = bw->GetBadWordCount(); i < end; ++i)
    231 			{
    232 				const BadWord *b = bw->GetBadWord(i);
    233 
    234 				if (!word.empty() && !Anope::Match(b->word, word))
    235 					continue;
    236 
    237 				ListFormatter::ListEntry entry;
    238 				entry["Number"] = stringify(i + 1);
    239 				entry["Word"] = b->word;
    240 				entry["Type"] = b->type == BW_SINGLE ? "(SINGLE)" : (b->type == BW_START ? "(START)" : (b->type == BW_END ? "(END)" : ""));
    241 				list.AddEntry(entry);
    242 			}
    243 		}
    244 
    245 		if (list.IsEmpty())
    246 			source.Reply(_("No matching entries on %s bad words list."), ci->name.c_str());
    247 		else
    248 		{
    249 			std::vector<Anope::string> replies;
    250 			list.Process(replies);
    251 
    252 			source.Reply(_("Bad words list for %s:"), ci->name.c_str());
    253 
    254 			for (unsigned i = 0; i < replies.size(); ++i)
    255 				source.Reply(replies[i]);
    256 
    257 			source.Reply(_("End of bad words list."));
    258 		}
    259 	}
    260 
    261 	void DoAdd(CommandSource &source, ChannelInfo *ci, const Anope::string &word)
    262 	{
    263 		size_t pos = word.rfind(' ');
    264 		BadWordType bwtype = BW_ANY;
    265 		Anope::string realword = word;
    266 		BadWords *badwords = ci->Require<BadWords>("badwords");
    267 
    268 		if (pos != Anope::string::npos)
    269 		{
    270 			Anope::string opt = word.substr(pos + 1);
    271 			if (!opt.empty())
    272 			{
    273 				if (opt.equals_ci("SINGLE"))
    274 					bwtype = BW_SINGLE;
    275 				else if (opt.equals_ci("START"))
    276 					bwtype = BW_START;
    277 				else if (opt.equals_ci("END"))
    278 					bwtype = BW_END;
    279 			}
    280 			realword = word.substr(0, pos);
    281 		}
    282 
    283 		unsigned badwordsmax = Config->GetModule(this->module)->Get<unsigned>("badwordsmax");
    284 		if (badwords->GetBadWordCount() >= badwordsmax)
    285 		{
    286 			source.Reply(_("Sorry, you can only have %d bad words entries on a channel."), badwordsmax);
    287 			return;
    288 		}
    289 
    290 		bool casesensitive = Config->GetModule(this->module)->Get<bool>("casesensitive");
    291 
    292 		for (unsigned i = 0, end = badwords->GetBadWordCount(); i < end; ++i)
    293 		{
    294 			const BadWord *bw = badwords->GetBadWord(i);
    295 
    296 			if ((casesensitive && realword.equals_cs(bw->word)) || (!casesensitive && realword.equals_ci(bw->word)))
    297 			{
    298 				source.Reply(_("\002%s\002 already exists in %s bad words list."), bw->word.c_str(), ci->name.c_str());
    299 				return;
    300 			}
    301 		}
    302 
    303 		bool override = !source.AccessFor(ci).HasPriv("BADWORDS");
    304 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "ADD " << realword;
    305 		badwords->AddBadWord(realword, bwtype);
    306 
    307 		source.Reply(_("\002%s\002 added to %s bad words list."), realword.c_str(), ci->name.c_str());
    308 	}
    309 
    310 	void DoDelete(CommandSource &source, ChannelInfo *ci, const Anope::string &word)
    311 	{
    312 		BadWords *badwords = ci->GetExt<BadWords>("badwords");
    313 
    314 		if (!badwords || !badwords->GetBadWordCount())
    315 		{
    316 			source.Reply(_("%s bad words list is empty."), ci->name.c_str());
    317 			return;
    318 		}
    319 
    320 		/* Special case: is it a number/list?  Only do search if it isn't. */
    321 		if (!word.empty() && isdigit(word[0]) && word.find_first_not_of("1234567890,-") == Anope::string::npos)
    322 		{
    323 			BadwordsDelCallback list(source, ci, this, word);
    324 			list.Process();
    325 		}
    326 		else
    327 		{
    328 			unsigned i, end;
    329 			const BadWord *badword;
    330 
    331 			for (i = 0, end = badwords->GetBadWordCount(); i < end; ++i)
    332 			{
    333 				badword = badwords->GetBadWord(i);
    334 
    335 				if (word.equals_ci(badword->word))
    336 					break;
    337 			}
    338 
    339 			if (i == end)
    340 			{
    341 				source.Reply(_("\002%s\002 not found on %s bad words list."), word.c_str(), ci->name.c_str());
    342 				return;
    343 			}
    344 
    345 			bool override = !source.AccessFor(ci).HasPriv("BADWORDS");
    346 			Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "DEL " << badword->word;
    347 
    348 			source.Reply(_("\002%s\002 deleted from %s bad words list."), badword->word.c_str(), ci->name.c_str());
    349 
    350 			badwords->EraseBadWord(i);
    351 		}
    352 
    353 		badwords->Check();
    354 	}
    355 
    356 	void DoClear(CommandSource &source, ChannelInfo *ci)
    357 	{
    358 		bool override = !source.AccessFor(ci).HasPriv("BADWORDS");
    359 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "CLEAR";
    360 
    361 		BadWords *badwords = ci->GetExt<BadWords>("badwords");
    362 		if (badwords)
    363 			badwords->ClearBadWords();
    364 		source.Reply(_("Bad words list is now empty."));
    365 	}
    366 
    367  public:
    368 	CommandBSBadwords(Module *creator) : Command(creator, "botserv/badwords", 2, 3)
    369 	{
    370 		this->SetDesc(_("Maintains the bad words list"));
    371 		this->SetSyntax(_("\037channel\037 ADD \037word\037 [\037SINGLE\037 | \037START\037 | \037END\037]"));
    372 		this->SetSyntax(_("\037channel\037 DEL {\037word\037 | \037entry-num\037 | \037list\037}"));
    373 		this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]"));
    374 		this->SetSyntax(_("\037channel\037 CLEAR"));
    375 	}
    376 
    377 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    378 	{
    379 		const Anope::string &cmd = params[1];
    380 		const Anope::string &word = params.size() > 2 ? params[2] : "";
    381 		bool need_args = cmd.equals_ci("LIST") || cmd.equals_ci("CLEAR");
    382 
    383 		if (!need_args && word.empty())
    384 		{
    385 			this->OnSyntaxError(source, cmd);
    386 			return;
    387 		}
    388 
    389 		ChannelInfo *ci = ChannelInfo::Find(params[0]);
    390 		if (ci == NULL)
    391 		{
    392 			source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
    393 			return;
    394 		}
    395 
    396 		if (!source.AccessFor(ci).HasPriv("BADWORDS") && !source.HasPriv("botserv/administration"))
    397 		{
    398 			source.Reply(ACCESS_DENIED);
    399 			return;
    400 		}
    401 
    402 		if (Anope::ReadOnly)
    403 		{
    404 			source.Reply(_("Sorry, bad words list modification is temporarily disabled."));
    405 			return;
    406 		}
    407 
    408 		if (cmd.equals_ci("ADD"))
    409 			return this->DoAdd(source, ci, word);
    410 		else if (cmd.equals_ci("DEL"))
    411 			return this->DoDelete(source, ci, word);
    412 		else if (cmd.equals_ci("LIST"))
    413 			return this->DoList(source, ci, word);
    414 		else if (cmd.equals_ci("CLEAR"))
    415 			return this->DoClear(source, ci);
    416 		else
    417 			this->OnSyntaxError(source, "");
    418 	}
    419 
    420 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    421 	{
    422 		this->SendSyntax(source);
    423 		source.Reply(" ");
    424 		source.Reply(_("Maintains the \002bad words list\002 for a channel. The bad\n"
    425 				"words list determines which words are to be kicked\n"
    426 				"when the bad words kicker is enabled. For more information,\n"
    427 				"type \002%s%s HELP KICK %s\002.\n"
    428 				" \n"
    429 				"The \002ADD\002 command adds the given word to the\n"
    430 				"bad words list. If SINGLE is specified, a kick will be\n"
    431 				"done only if a user says the entire word. If START is\n"
    432 				"specified, a kick will be done if a user says a word\n"
    433 				"that starts with \037word\037. If END is specified, a kick\n"
    434 				"will be done if a user says a word that ends with\n"
    435 				"\037word\037. If you don't specify anything, a kick will\n"
    436 				"be issued every time \037word\037 is said by a user.\n"
    437 				" \n"), Config->StrictPrivmsg.c_str(), source.service->nick.c_str(), source.command.c_str());
    438 		source.Reply(_("The \002DEL\002 command removes the given word from the\n"
    439 				"bad words list.  If a list of entry numbers is given, those\n"
    440 				"entries are deleted.  (See the example for LIST below.)\n"
    441 				" \n"
    442 				"The \002LIST\002 command displays the bad words list.  If\n"
    443 				"a wildcard mask is given, only those entries matching the\n"
    444 				"mask are displayed.  If a list of entry numbers is given,\n"
    445 				"only those entries are shown; for example:\n"
    446 				"   \002#channel LIST 2-5,7-9\002\n"
    447 				"      Lists bad words entries numbered 2 through 5 and\n"
    448 				"      7 through 9.\n"
    449 				" \n"
    450 				"The \002CLEAR\002 command clears all entries from the\n"
    451 				"bad words list."));
    452 		return true;
    453 	}
    454 };
    455 
    456 class BSBadwords : public Module
    457 {
    458 	CommandBSBadwords commandbsbadwords;
    459 	ExtensibleItem<BadWordsImpl> badwords;
    460 	Serialize::Type badword_type;
    461 
    462  public:
    463 	BSBadwords(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    464 		commandbsbadwords(this), badwords(this, "badwords"), badword_type("BadWord", BadWordImpl::Unserialize)
    465 	{
    466 	}
    467 };
    468 
    469 MODULE_INIT(BSBadwords)