diff options
| author | Adam <Adam@anope.org> | 2015-12-14 16:52:24 -0500 |
|---|---|---|
| committer | Adam <Adam@anope.org> | 2015-12-14 16:52:24 -0500 |
| commit | 6302f980fe6dad83aab7d2dc1046dadea5ffd939 (patch) | |
| tree | 4c27d33f55fc32ce34827977932dac4b3e5ceedd /modules/botserv | |
| parent | 64dac60071fab652745a6e7a06cf6b7bdbbd3625 (diff) | |
New source tree structure for modules. From commands/cs_access => chanserv/access etc.
Diffstat (limited to 'modules/botserv')
| -rw-r--r-- | modules/botserv/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | modules/botserv/assign.cpp | 281 | ||||
| -rw-r--r-- | modules/botserv/autoassign.cpp | 37 | ||||
| -rw-r--r-- | modules/botserv/badwords.cpp | 465 | ||||
| -rw-r--r-- | modules/botserv/bot.cpp | 426 | ||||
| -rw-r--r-- | modules/botserv/botlist.cpp | 92 | ||||
| -rw-r--r-- | modules/botserv/control.cpp | 168 | ||||
| -rw-r--r-- | modules/botserv/info.cpp | 116 | ||||
| -rw-r--r-- | modules/botserv/kick.cpp | 1664 | ||||
| -rw-r--r-- | modules/botserv/main/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | modules/botserv/main/botserv.cpp | 228 | ||||
| -rw-r--r-- | modules/botserv/set.cpp | 221 |
12 files changed, 3700 insertions, 0 deletions
diff --git a/modules/botserv/CMakeLists.txt b/modules/botserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/botserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/botserv/assign.cpp b/modules/botserv/assign.cpp new file mode 100644 index 000000000..840651dfc --- /dev/null +++ b/modules/botserv/assign.cpp @@ -0,0 +1,281 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/bs_info.h" + +class CommandBSAssign : public Command +{ + public: + CommandBSAssign(Module *creator) : Command(creator, "botserv/assign", 2, 2) + { + this->SetDesc(_("Assigns a bot to a channel")); + this->SetSyntax(_("\037channel\037 \037nickname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &nick = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, bot assignment is temporarily disabled.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + ServiceBot *bi = ServiceBot::Find(nick, true); + if (!bi) + { + source.Reply(_("Bot \002{0}\002 does not exist."), nick); + return; + } + + ChanServ::AccessGroup access = source.AccessFor(ci); + if (!access.HasPriv("ASSIGN") && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ASSIGN", ci->GetName()); + return; + } + + if (ci->HasFieldS("BS_NOBOT")) + { + source.Reply(_("Access denied. \002{0}\002 may not have a bot assigned to it because a Services Operator has disallowed it."), ci->GetName()); + return; + } + + if (bi->bi->GetOperOnly() && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. Bot \002{0}\002 is for operators only."), bi->nick); + return; + } + + if (ci->GetBot() == bi) + { + source.Reply(_("Bot \002{0}\002 is already assigned to \002{1}\002."), ci->GetBot()->nick, ci->GetName()); + return; + } + + bool override = !access.HasPriv("ASSIGN"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << bi->nick; + + bi->Assign(source.GetUser(), ci); + source.Reply(_("Bot \002{0}\002 has been assigned to \002{1}\002."), bi->nick, ci->GetName()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Assigns the bot \037nickname\037 to \037channel\037." + " You can then configure the bot for the channel so it fits your needs.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037." + "\n" + "Example:\n" + " {command} #anope Botox\n" + " Assigns the bot Botox to #anope.\n"), + "ASSIGN", "command"_kw = source.command); + return true; + } +}; + +class CommandBSUnassign : public Command +{ + public: + CommandBSUnassign(Module *creator) : Command(creator, "botserv/unassign", 1, 1) + { + this->SetDesc(_("Unassigns a bot from a channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &chan = params[0]; + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, bot assignment is temporarily disabled.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + ChanServ::AccessGroup access = source.AccessFor(ci); + if (!source.HasPriv("botserv/administration") && !access.HasPriv("ASSIGN")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ASSIGN", ci->GetName()); + return; + } + + if (!ci->GetBot()) + { + source.Reply(_("There is no bot assigned to \002{0}\002."), ci->GetName()); + return; + } + + if (ci->HasFieldS("PERSIST") && !ModeManager::FindChannelModeByName("PERM")) + { + source.Reply(_("You cannot unassign bots while persist is set on the channel.")); + return; + } + + bool override = !access.HasPriv("ASSIGN"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << ci->GetBot()->nick; + + ServiceBot *bi = ci->GetBot(); + bi->UnAssign(source.GetUser(), ci); + source.Reply(_("Bot \002{0}\002 has been unassigned from \002{1}\002."), bi->nick, ci->GetName()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Unassigns a bot from \037channel\037." + "Bot configuration is kept, so you will always be able to reassign a bot later without losing your settings.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037." + "\n" + "Example:\n" + " {command} #anope\n" + " Unassigns the current bot from #anope.\n"), + "ASSIGN", "command"_kw = source.command); + return true; + } +}; + +class CommandBSSetNoBot : public Command +{ + public: + CommandBSSetNoBot(Module *creator, const Anope::string &sname = "botserv/set/nobot") : Command(creator, sname, 2, 2) + { + this->SetDesc(_("Prevent a bot from being assigned to a channel")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &value = params[1]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (value.equals_ci("ON")) + { + Log(LOG_ADMIN, source, this, ci) << "to enable nobot"; + + ci->SetS<bool>("BS_NOBOT", true); + if (ci->GetBot()) + ci->GetBot()->UnAssign(source.GetUser(), ci); + source.Reply(_("No-bot mode is now \002on\002 for \002{0}\002."), ci->GetName()); + } + else if (value.equals_ci("OFF")) + { + Log(LOG_ADMIN, source, this, ci) << "to disable nobot"; + + ci->UnsetS<bool>("BS_NOBOT"); + source.Reply(_("No-bot mode is now \002off\002 for \002{0}\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, source.command); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("If no-bot is set on a channel, then the channel will be unabled to have a bot assigned to it." + " If a bot is already assigned to the channel, it is unassigned automatically when no-bot is enabled." + "\n" + "Example:\n" + " {command} #anope on\n" + " Prevents a service bot from being assigned to #anope.\n"), + "command"_kw = source.command); + return true; + } +}; + +class BSAssign : public Module + , public EventHook<Event::Invite> + , public EventHook<Event::ServiceBotEvent> +{ + Serialize::Field<BotInfo, bool> nobot; + + CommandBSAssign commandbsassign; + CommandBSUnassign commandbsunassign; + CommandBSSetNoBot commandbssetnobot; + + public: + BSAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + + , nobot(this, botinfo, "BS_NOBOT") + + , commandbsassign(this) + , commandbsunassign(this) + , commandbssetnobot(this) + { + } + + void OnInvite(User *source, Channel *c, User *targ) override + { + ServiceBot *bi; + if (Anope::ReadOnly || !c->ci || targ->server != Me || !(bi = ServiceBot::Find(targ->nick, true))) + return; + + ChanServ::AccessGroup access = c->ci->AccessFor(source); + if (!access.HasPriv("ASSIGN") && !source->HasPriv("botserv/administration")) + { + targ->SendMessage(bi, _("Access denied. You do not have privilege \002ASSIGN\002 on \002{0}\002."), c->ci->GetName()); + return; + } + + if (nobot.HasExt(c->ci)) + { + targ->SendMessage(bi, _("Access denied. \002{0}\002 may not have a bot assigned to it because a Services Operator has disallowed it."), c->ci->GetName()); + return; + } + + if (bi->bi->GetOperOnly() && !source->HasPriv("botserv/administration")) + { + targ->SendMessage(bi, _("Access denied. Bot \002{0}\002 is for operators only."), bi->nick); + return; + } + + if (c->ci->GetBot() == bi) + { + targ->SendMessage(bi, _("Bot \002{0}\002 is already assigned to \002{1}\002."), bi->nick, c->ci->GetName()); + return; + } + + bi->Assign(source, c->ci); + targ->SendMessage(bi, _("Bot \002{0}\002 has been assigned to \002{1}\002."), bi->nick, c->ci->GetName()); + } + + void OnServiceBot(CommandSource &source, ServiceBot *bi, ChanServ::Channel *ci, InfoFormatter &info) override + { + if (nobot.HasExt(ci)) + info.AddOption(_("No bot")); + } +}; + +MODULE_INIT(BSAssign) diff --git a/modules/botserv/autoassign.cpp b/modules/botserv/autoassign.cpp new file mode 100644 index 000000000..8978499ed --- /dev/null +++ b/modules/botserv/autoassign.cpp @@ -0,0 +1,37 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + */ + +#include "module.h" + +class BSAutoAssign : public Module + , public EventHook<Event::ChanRegistered> +{ + public: + BSAutoAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + { + } + + void OnChanRegistered(ChanServ::Channel *ci) override + { + const Anope::string &bot = Config->GetModule(this)->Get<Anope::string>("bot"); + if (bot.empty()) + return; + + ServiceBot *bi = ServiceBot::Find(bot, true); + if (bi == NULL) + { + Log(this) << "bs_autoassign is configured to assign bot " << bot << ", but it does not exist?"; + return; + } + + bi->Assign(NULL, ci); + } +}; + +MODULE_INIT(BSAutoAssign) diff --git a/modules/botserv/badwords.cpp b/modules/botserv/badwords.cpp new file mode 100644 index 000000000..0f8e9e7d0 --- /dev/null +++ b/modules/botserv/badwords.cpp @@ -0,0 +1,465 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/bs_badwords.h" + +static EventHandlers<Event::BadWordEvents> *bwevents; + +class BadWordImpl : public BadWord +{ + public: + BadWordImpl(Serialize::TypeBase *type) : BadWord(type) { } + BadWordImpl(Serialize::TypeBase *type, Serialize::ID id) : BadWord(type, id) { } + + ChanServ::Channel *GetChannel() override; + + void SetChannel(ChanServ::Channel *c) override; + + Anope::string GetWord() override; + + void SetWord(const Anope::string &w) override; + + BadWordType GetType() override; + + void SetType(const BadWordType &t) override; +}; + +class BadWordsType : public Serialize::Type<BadWordImpl> +{ + public: + Serialize::ObjectField<BadWordImpl, ChanServ::Channel *> channel; + Serialize::Field<BadWordImpl, Anope::string> word; + Serialize::Field<BadWordImpl, BadWordType> type; + + BadWordsType(Module *me) : Serialize::Type<BadWordImpl>(me, "BadWord") + , channel(this, "ci", true) + , word(this, "word") + , type(this, "type") + { + } +}; + +ChanServ::Channel *BadWordImpl::GetChannel() +{ + return Get(&BadWordsType::channel); +} + +void BadWordImpl::SetChannel(ChanServ::Channel *c) +{ + Set(&BadWordsType::channel, c); +} + +Anope::string BadWordImpl::GetWord() +{ + return Get(&BadWordsType::word); +} + +void BadWordImpl::SetWord(const Anope::string &w) +{ + Set(&BadWordsType::word, w); +} + +BadWordType BadWordImpl::GetType() +{ + return Get(&BadWordsType::type); +} + +void BadWordImpl::SetType(const BadWordType &t) +{ + Set(&BadWordsType::type, t); +} + +struct BadWordsImpl : BadWords +{ + BadWordsImpl(Module *me) : BadWords(me) { } + + BadWord* AddBadWord(ChanServ::Channel *ci, const Anope::string &word, BadWordType type) override + { + BadWord *bw = badword.Create(); + bw->SetChannel(ci); + bw->SetWord(word); + bw->SetType(type); + + (*bwevents)(&Event::BadWordEvents::OnBadWordAdd, ci, bw); + + return bw; + } + + std::vector<BadWord *> GetBadWords(ChanServ::Channel *ci) + { + return ci->GetRefs<BadWord *>(badword); + } + + BadWord* GetBadWord(ChanServ::Channel *ci, unsigned index) override + { + auto bw = GetBadWords(ci); + return index < bw.size() ? bw[index] : nullptr; + } + + unsigned GetBadWordCount(ChanServ::Channel *ci) override + { + return GetBadWords(ci).size(); + } + + void EraseBadWord(ChanServ::Channel *ci, unsigned index) override + { + auto bws = GetBadWords(ci); + if (index >= bws.size()) + return; + + BadWord *bw = bws[index]; + (*bwevents)(&Event::BadWordEvents::OnBadWordDel, ci, bw); + + delete bw; + } + + void ClearBadWords(ChanServ::Channel *ci) override + { + for (BadWord *bw : GetBadWords(ci)) + delete bw; + } +}; + +class CommandBSBadwords : public Command +{ + private: + void DoList(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) + { + bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "LIST"; + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Number")).AddColumn(_("Word")).AddColumn(_("Type")); + + if (!badwords->GetBadWordCount(ci)) + { + source.Reply(_("The bad word list of \002{0}\002 is empty."), ci->GetName()); + return; + } + + if (!word.empty() && word.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(word, false, + [&](unsigned int num) + { + if (!num || num > badwords->GetBadWordCount(ci)) + return; + + BadWord *b = badwords->GetBadWord(ci, num - 1); + ListFormatter::ListEntry entry; + entry["Number"] = stringify(num); + entry["Word"] = b->GetWord(); + entry["Type"] = b->GetType() == BW_SINGLE ? "(SINGLE)" : (b->GetType() == BW_START ? "(START)" : (b->GetType() == BW_END ? "(END)" : "")); + list.AddEntry(entry); + }, + [](){}); + } + else + { + for (unsigned i = 0, end = badwords->GetBadWordCount(ci); i < end; ++i) + { + BadWord *b = badwords->GetBadWord(ci, i); + + if (!word.empty() && !Anope::Match(b->GetWord(), word)) + continue; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Word"] = b->GetWord(); + entry["Type"] = b->GetType() == BW_SINGLE ? "(SINGLE)" : (b->GetType() == BW_START ? "(START)" : (b->GetType() == BW_END ? "(END)" : "")); + list.AddEntry(entry); + } + } + + if (list.IsEmpty()) + { + source.Reply(_("No matching entries on the bad word list of \002{0}\002."), ci->GetName()); + return; + } + + std::vector<Anope::string> replies; + list.Process(replies); + + source.Reply(_("Bad words list for \002{0}\002:"), ci->GetName()); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of bad words list.")); + } + + void DoAdd(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) + { + size_t pos = word.rfind(' '); + BadWordType bwtype = BW_ANY; + Anope::string realword = word; + + if (pos != Anope::string::npos) + { + Anope::string opt = word.substr(pos + 1); + if (!opt.empty()) + { + if (opt.equals_ci("SINGLE")) + bwtype = BW_SINGLE; + else if (opt.equals_ci("START")) + bwtype = BW_START; + else if (opt.equals_ci("END")) + bwtype = BW_END; + } + realword = word.substr(0, pos); + } + + unsigned badwordsmax = Config->GetModule(this->module)->Get<unsigned>("badwordsmax"); + if (badwords->GetBadWordCount(ci) >= badwordsmax) + { + source.Reply(_("Sorry, you can only have \002{0}\002 bad words entries on a channel."), badwordsmax); + return; + } + + bool casesensitive = Config->GetModule(this->module)->Get<bool>("casesensitive"); + + for (BadWord *bw : badwords->GetBadWords(ci)) + if ((casesensitive && realword.equals_cs(bw->GetWord())) || (!casesensitive && realword.equals_ci(bw->GetWord()))) + { + source.Reply(_("\002{0}\002 already exists in \002{1}\002 bad words list."), bw->GetWord(), ci->GetName()); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "ADD " << realword; + badwords->AddBadWord(ci, realword, bwtype); + + source.Reply(_("\002{0}\002 added to \002{1}\002 bad words list."), realword, ci->GetName()); + } + + void DoDelete(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) + { + if (!badwords->GetBadWordCount(ci)) + { + source.Reply(_("Bad word list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (!word.empty() && isdigit(word[0]) && word.find_first_not_of("1234567890,-") == Anope::string::npos) + { + unsigned int deleted = 0; + + NumberList(word, true, + [&](unsigned int num) + { + if (!num || num > badwords->GetBadWordCount(ci)) + return; + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "DEL " << badwords->GetBadWord(ci, num - 1)->GetWord(); + ++deleted; + badwords->EraseBadWord(ci, num - 1); + }, + [&]() + { + if (!deleted) + source.Reply(_("No matching entries on the bad word list of \002{0}\002."), ci->GetName()); + else if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from bad word list of \002{0}\002."), ci->GetName()); + else + source.Reply(_("Deleted \002{0}\002 entries from the bad word list of \002{1}\002."), deleted, ci->GetName()); + }); + } + else + { + unsigned i, end; + BadWord *bw; + + for (i = 0, end = badwords->GetBadWordCount(ci); i < end; ++i) + { + bw = badwords->GetBadWord(ci, i); + + if (word.equals_ci(bw->GetWord())) + break; + } + + if (i == end) + { + source.Reply(_("\002{0}\002 was not found on the bad word list of \002{1}\002."), word, ci->GetName()); + return; + } + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "DEL " << bw->GetWord(); + + source.Reply(_("\002{0}\002 deleted from \002{1}\002 bad words list."), bw->GetWord(), ci->GetName()); + + bw->Delete(); + } + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "CLEAR"; + + badwords->ClearBadWords(ci); + source.Reply(_("Bad words list is now empty.")); + } + + public: + CommandBSBadwords(Module *creator) : Command(creator, "botserv/badwords", 2, 3) + { + this->SetDesc(_("Maintains the bad words list")); + this->SetSyntax(_("\037channel\037 ADD \037word\037 [\037SINGLE\037 | \037START\037 | \037END\037]")); + this->SetSyntax(_("\037channel\037 DEL {\037word\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params[1]; + const Anope::string &word = params.size() > 2 ? params[2] : ""; + bool need_args = cmd.equals_ci("LIST") || cmd.equals_ci("CLEAR"); + + if (!need_args && word.empty()) + { + this->OnSyntaxError(source, cmd); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("BADWORDS") && (!need_args || !source.HasPriv("botserv/administration"))) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "BADWORDS", ci->GetName()); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, bad words list modification is temporarily disabled.")); + return; + } + + if (cmd.equals_ci("ADD")) + this->DoAdd(source, ci, word); + else if (cmd.equals_ci("DEL")) + this->DoDelete(source, ci, word); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, word); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("Adds \037word\037 to the bad words list for \037channel\037. If \002SINGLE\002 is specified then the user must say the enire word for it to be considered a match." + " If \002START\002 is specified then the user only must say a word that starts with \037word\037." + " Likewise if \002END\002 is specified then the user must only say a word that ends with \037word\037." + " If no argument is specified, then any word which contains \037word\037 will be considered a match.\n" + "\n" + "Examples:\n" + " {command} #anope ADD raw SINGLE\n" + " Adds the bad word \"raw\" to the bad word list of #anope."), + "command"_kw = source.command); + } + else if (subcommand.equals_ci("DEL")) + { + source.Reply(_("Removes \037word\037 from the bad words list for \037channel\037. If a number or list of numbers is given, those entries are deleted.\n" + "\n" + "Examples:\n" + " {command} #anope DEL raw\n" + " Removes the bad word \"raw\" from the bad word list of #anope.\n" + "\n" + " {command} #anope DEL 2-5,7-9\n" + " Removes bad words entries numbered 2 through 5 and 7 through 9 on #anope."), + "command"_kw = source.command); + + } + else if (subcommand.equals_ci("LIST")) + { + source.Reply(_("Lists the badwords for \037channel\037." + " If a wildcard mask is given, only those entries matching the mask are displayed." + " If a list of entry numbers is given, only those entries are shown.\n" + "\n" + "Examples:\n" + " {command} #anope LIST\n" + " Lists the bad words for #anope.\n" + "\n" + " {command} #anope LIST 2-5,7-9\n" + " Lists bad words entries numbered 2 thorough 5 and 7 through 9 on #anope."), + "command"_kw = source.command); + } + else if (subcommand.equals_ci("CLEAR")) + { + source.Reply(_("Clears the bad words for \037channel\037." + "\n" + "\n" + "Example:\n" + " {command} #anope CLEAR\n" + " Clears the bad word list for #anope."), + "command"_kw = source.command); + } + else + { + source.Reply(_("Maintains the bad words list for a channel." + " When a word on the bad words list is said, action may be taken against the offending user.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037."), + "BADWORDS"); + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("\n" + "For help configuring the bad word kicker use \002{msg}{service} HELP KICK BADWORDS\002.\n"//XXX + "\n" + "The \002ADD\002 command adds \037word\037 to the badwords list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037word\037 from the badwords list.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002CLEAR\002 commands show and clear the bad words list, respectively.\n" + "\002{msg}{service} {help} {command} LIST\002 and \002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "command"_kw = source.command, "help"_kw = help->cname); + } + return true; + } +}; + +class BSBadwords : public Module +{ + CommandBSBadwords commandbsbadwords; + BadWordsImpl badwords; + EventHandlers<Event::BadWordEvents> events; + BadWordsType bwtype; + + public: + BSBadwords(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsbadwords(this) + , badwords(this) + , events(this) + , bwtype(this) + { + bwevents = &events; + } +}; + +MODULE_INIT(BSBadwords) diff --git a/modules/botserv/bot.cpp b/modules/botserv/bot.cpp new file mode 100644 index 000000000..726c02ab2 --- /dev/null +++ b/modules/botserv/bot.cpp @@ -0,0 +1,426 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/bs_bot.h" + +class CommandBSBot : public Command +{ + EventHandlers<Event::BotCreate> OnBotCreate; + EventHandlers<Event::BotChange> OnBotChange; + EventHandlers<Event::BotDelete> OnBotDelete; + + void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &nick = params[1]; + const Anope::string &user = params[2]; + const Anope::string &host = params[3]; + const Anope::string &real = params[4]; + + if (ServiceBot::Find(nick, true)) + { + source.Reply(_("Bot \002{0}\002 already exists."), nick); + return; + } + + Configuration::Block *networkinfo = Config->GetBlock("networkinfo"); + + if (nick.length() > networkinfo->Get<unsigned>("nicklen")) + { + source.Reply(_("Bot nicks may only be \002{0}\002 characters long."), networkinfo->Get<unsigned>("nicklen")); + return; + } + + if (user.length() > networkinfo->Get<unsigned>("userlen")) + { + source.Reply(_("Bot idents may only be \002{0}\002 characters long."), networkinfo->Get<unsigned>("userlen")); + return; + } + + if (host.length() > networkinfo->Get<unsigned>("hostlen")) + { + source.Reply(_("Bot hosts may only be \002{0}\002 characters long."), networkinfo->Get<unsigned>("hostlen")); + return; + } + + if (!IRCD->IsNickValid(nick)) + { + source.Reply(_("Bot nicks may only contain valid nick characters.")); + return; + } + + if (!IRCD->IsIdentValid(user)) + { + source.Reply(_("Bot idents may only contain valid ident characters.")); + return; + } + + if (!IRCD->IsHostValid(host)) + { + source.Reply(_("Bot hosts may only contain valid host characters.")); + return; + } + + /* We check whether the nick is registered, and inform the user + * if so. You need to drop the nick manually before you can use + * it as a bot nick from now on -GD + */ + NickServ::Nick *na = NickServ::FindNick(nick); + if (na) + { + source.Reply(_("\002{0}\002 is already registered!"), na->GetNick()); + return; + } + + User *targ = User::Find(nick, true); + if (targ) + { + source.Reply(_("\002{0}\002 is currently in use."), targ->nick); + return; + } + + ServiceBot *bi = new ServiceBot(nick, user, host, real); + + Log(LOG_ADMIN, source, this) << "ADD " << bi->GetMask() << " " << bi->realname; + + source.Reply(_("\002{0}!{1}@{2}\002 (\002{3}\002) added to the bot list."), bi->nick, bi->GetIdent(), bi->host, bi->realname); + + this->OnBotCreate(&Event::BotCreate::OnBotCreate, bi); + } + + void DoChange(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &oldnick = params[1]; + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + const Anope::string &user = params.size() > 3 ? params[3] : ""; + const Anope::string &host = params.size() > 4 ? params[4] : ""; + const Anope::string &real = params.size() > 5 ? params[5] : ""; + + if (oldnick.empty() || nick.empty()) + { + this->OnSyntaxError(source, "CHANGE"); + return; + } + + ServiceBot *bi = ServiceBot::Find(oldnick, true); + if (!bi) + { + source.Reply(_("Bot \002{0}\002 does not exist."), oldnick); + return; + } + + if (bi->bi->conf) + { + source.Reply(_("Bot \002{0}\002 is not changeable because it is configured in services configuration."), bi->nick.c_str()); + return; + } + + Configuration::Block *networkinfo = Config->GetBlock("networkinfo"); + + if (nick.length() > networkinfo->Get<unsigned>("nicklen")) + { + source.Reply(_("Bot nicknames may only be \002{0}\002 characters long."), networkinfo->Get<unsigned>("nicklen")); + return; + } + + if (user.length() > networkinfo->Get<unsigned>("userlen")) + { + source.Reply(_("Bot usernames may only be \002{0}\002 characters long."), networkinfo->Get<unsigned>("userlen")); + return; + } + + if (host.length() > networkinfo->Get<unsigned>("hostlen")) + { + source.Reply(_("Bot hostnames may only be \002{0}\002 characters long."), networkinfo->Get<unsigned>("hostlen")); + return; + } + + /* Checks whether there *are* changes. + * Case sensitive because we may want to change just the case. + * And we must finally check that the nick is not already + * taken by another bot. + */ + if (nick.equals_cs(bi->nick) + && (user.empty() || user.equals_cs(bi->GetIdent())) + && (host.empty() || host.equals_cs(bi->host)) + && (real.empty() || real.equals_cs(bi->realname))) + { + source.Reply(_("There is no difference between the current settings and the new settings.")); + return; + } + + if (!IRCD->IsNickValid(nick)) + { + source.Reply(_("Bot nicknames may only contain valid nickname characters.")); + return; + } + + if (!user.empty() && |
