summaryrefslogtreecommitdiff
path: root/modules/botserv
diff options
context:
space:
mode:
authorAdam <Adam@anope.org>2015-12-14 16:52:24 -0500
committerAdam <Adam@anope.org>2015-12-14 16:52:24 -0500
commit6302f980fe6dad83aab7d2dc1046dadea5ffd939 (patch)
tree4c27d33f55fc32ce34827977932dac4b3e5ceedd /modules/botserv
parent64dac60071fab652745a6e7a06cf6b7bdbbd3625 (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.txt1
-rw-r--r--modules/botserv/assign.cpp281
-rw-r--r--modules/botserv/autoassign.cpp37
-rw-r--r--modules/botserv/badwords.cpp465
-rw-r--r--modules/botserv/bot.cpp426
-rw-r--r--modules/botserv/botlist.cpp92
-rw-r--r--modules/botserv/control.cpp168
-rw-r--r--modules/botserv/info.cpp116
-rw-r--r--modules/botserv/kick.cpp1664
-rw-r--r--modules/botserv/main/CMakeLists.txt1
-rw-r--r--modules/botserv/main/botserv.cpp228
-rw-r--r--modules/botserv/set.cpp221
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> &params) 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> &params) 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> &params) 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> &params) 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> &params)
+ {
+ 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> &params)
+ {
+ 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() &&