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> ¶ms) 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)