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