From 6302f980fe6dad83aab7d2dc1046dadea5ffd939 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 14 Dec 2015 16:52:24 -0500 Subject: New source tree structure for modules. From commands/cs_access => chanserv/access etc. --- modules/botserv/CMakeLists.txt | 1 + modules/botserv/assign.cpp | 281 ++++++ modules/botserv/autoassign.cpp | 37 + modules/botserv/badwords.cpp | 465 ++++++++++ modules/botserv/bot.cpp | 426 +++++++++ modules/botserv/botlist.cpp | 92 ++ modules/botserv/control.cpp | 168 ++++ modules/botserv/info.cpp | 116 +++ modules/botserv/kick.cpp | 1664 +++++++++++++++++++++++++++++++++++ modules/botserv/main/CMakeLists.txt | 1 + modules/botserv/main/botserv.cpp | 228 +++++ modules/botserv/set.cpp | 221 +++++ 12 files changed, 3700 insertions(+) create mode 100644 modules/botserv/CMakeLists.txt create mode 100644 modules/botserv/assign.cpp create mode 100644 modules/botserv/autoassign.cpp create mode 100644 modules/botserv/badwords.cpp create mode 100644 modules/botserv/bot.cpp create mode 100644 modules/botserv/botlist.cpp create mode 100644 modules/botserv/control.cpp create mode 100644 modules/botserv/info.cpp create mode 100644 modules/botserv/kick.cpp create mode 100644 modules/botserv/main/CMakeLists.txt create mode 100644 modules/botserv/main/botserv.cpp create mode 100644 modules/botserv/set.cpp (limited to 'modules/botserv') 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 ¶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 ¶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 ¶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("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("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 + , public EventHook +{ + Serialize::Field 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 +{ + 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("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 *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 +{ + public: + Serialize::ObjectField channel; + Serialize::Field word; + Serialize::Field type; + + BadWordsType(Module *me) : Serialize::Type(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 GetBadWords(ChanServ::Channel *ci) + { + return ci->GetRefs(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 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("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("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 ¶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 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 OnBotCreate; + EventHandlers OnBotChange; + EventHandlers OnBotDelete; + + void DoAdd(CommandSource &source, const std::vector ¶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("nicklen")) + { + source.Reply(_("Bot nicks may only be \002{0}\002 characters long."), networkinfo->Get("nicklen")); + return; + } + + if (user.length() > networkinfo->Get("userlen")) + { + source.Reply(_("Bot idents may only be \002{0}\002 characters long."), networkinfo->Get("userlen")); + return; + } + + if (host.length() > networkinfo->Get("hostlen")) + { + source.Reply(_("Bot hosts may only be \002{0}\002 characters long."), networkinfo->Get("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 ¶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("nicklen")) + { + source.Reply(_("Bot nicknames may only be \002{0}\002 characters long."), networkinfo->Get("nicklen")); + return; + } + + if (user.length() > networkinfo->Get("userlen")) + { + source.Reply(_("Bot usernames may only be \002{0}\002 characters long."), networkinfo->Get("userlen")); + return; + } + + if (host.length() > networkinfo->Get("hostlen")) + { + source.Reply(_("Bot hostnames may only be \002{0}\002 characters long."), networkinfo->Get("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() && !IRCD->IsIdentValid(user)) + { + source.Reply(_("Bot uesrnames may only contain valid username characters.")); + return; + } + + if (!host.empty() && !IRCD->IsHostValid(host)) + { + source.Reply(_("Bot hostnames may only contain valid hostname characters.")); + return; + } + + ServiceBot *newbi = ServiceBot::Find(nick, true); + if (newbi && bi != newbi) + { + source.Reply(_("Bot \002{0}\002 already exists."), newbi->nick); + return; + } + + User *target = User::Find(nick, true); + if (target) + { + source.Reply(_("\002{0}\002 is currently in use."), target->nick); + return; + } + + if (!nick.equals_ci(bi->nick)) + { + /* 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; + } + + /* The new nick is really different, so we remove the Q line for the old nick. */ + //XLine x_del(bi->nick); + //IRCD->SendSQLineDel(&x_del); + + /* Add a Q line for the new nick */ + //XLine x(nick, "Reserved for services"); + //IRCD->SendSQLine(NULL, &x); + } + + if (!user.empty()) + { + IRCD->SendQuit(bi, "Quit: Be right back"); + bi->introduced = false; + } + else + IRCD->SendNickChange(bi, nick); + + if (!nick.equals_cs(bi->nick)) + { + bi->SetNewNick(nick); + bi->bi->SetNick(nick); + } + + if (!user.equals_cs(bi->GetIdent())) + { + bi->SetIdent(user); + bi->bi->SetUser(user); + } + if (!host.equals_cs(bi->host)) + { + bi->host = host; + bi->bi->SetHost(host); + } + if (real.equals_cs(bi->realname)) + { + bi->realname = real; + bi->bi->SetRealName(real); + } + + if (!user.empty()) + bi->OnKill(); + + source.Reply(_("Bot \002{0}\002 has been changed to \002{1}!{2}@{3}\002 (\002{4}\002)."), oldnick, bi->nick, bi->GetIdent(), bi->host, bi->realname); + Log(LOG_ADMIN, source, this) << "CHANGE " << oldnick << " to " << bi->GetMask() << " " << bi->realname; + + this->OnBotChange(&Event::BotChange::OnBotChange, bi); + } + + void DoDel(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &nick = params[1]; + + if (nick.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + ServiceBot *bi = ServiceBot::Find(nick, true); + if (!bi) + { + source.Reply(_("Bot \002{0}\002 does not exist."), nick); + return; + } + + if (bi->bi->conf) + { + source.Reply(_("Bot \002{0}\002 is can not be deleted because it is configured in services configuration."), bi->nick); + return; + } + + this->OnBotDelete(&Event::BotDelete::OnBotDelete, bi); + + Log(LOG_ADMIN, source, this) << "DEL " << bi->nick; + + source.Reply(_("Bot \002{0}\002 has been deleted."), bi->nick); + delete bi; + } + + public: + CommandBSBot(Module *creator) : Command(creator, "botserv/bot", 1, 6) + , OnBotCreate(creator) + , OnBotChange(creator) + , OnBotDelete(creator) + { + this->SetDesc(_("Maintains network bot list")); + this->SetSyntax(_("\002ADD \037nicknae\037 \037username\037 \037hostname\037 \037realname\037\002")); + this->SetSyntax(_("\002CHANGE \037oldnickname\037 \037newnickname\037 [\037username\037 [\037hostname\037 [\037realname\037]]]\002")); + this->SetSyntax(_("\002DEL \037nickname\037\002")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (cmd.equals_ci("ADD")) + { + if (!source.HasCommand("botserv/bot/add")) + { + source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/add"); + return; + } + + // ADD nick user host real - 5 + if (params.size() < 5) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + std::vector tempparams = params; + // ADD takes less params than CHANGE, so we need to take 6 if given and append it with a space to 5. + if (tempparams.size() >= 6) + tempparams[4] = tempparams[4] + " " + tempparams[5]; + + this->DoAdd(source, tempparams); + } + else if (cmd.equals_ci("CHANGE")) + { + // CHANGE oldn newn user host real - 6 + // but only oldn and newn are required + if (!source.HasCommand("botserv/bot/change")) + { + source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/change"); + return; + } + + if (params.size() < 3) + { + this->OnSyntaxError(source, "CHANGE"); + return; + } + + this->DoChange(source, params); + } + else if (cmd.equals_ci("DEL")) + { + // DEL nick + if (!source.HasCommand("botserv/bot/del")) + { + source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/del"); + return; + } + + if (params.size() < 1) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + this->DoDel(source, params); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + source.Reply(_("\002{command} ADD\002 adds a bot with the given \037nickname\037, \037username\037, \037hostname\037 and \037realname\037." + " You can not create a bot with a nickname that is currently registered. If an unregistered user is currently using the nick, they will be killed.\n" + " Once a bot is created, users will be able to assign the bot to their channels. This command requires the opererator privilege for command \002{0}\002." + "\n" + "Example:\n" + " {command} ADD Botox Botox services.anope.org Botox\n" + " Adds a service bot with nickname \"Botox\", username \"Botox\", hostname \"services.anope.org\", and realname \"Botox\" to the bot list."), + "botserv/bot/add", "command"_kw = source.command); + else if (subcommand.equals_ci("CHANGE")) + source.Reply(_("\002{command} CHANGE\002 allows changing the \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 of bot \037oldnickname\037." + " If a new username, hostname, or realname is specified, then the bot \037nickname\037 will quit and rejoin all of its channels using the new mask." + " Otherwise, the bot simply change nick to \037newnickname\037. All settings on the bot, such as channels and no-bot, are retained." + " This command requires the operator privilege for command \002{0}\002." + "\n" + "Example:\n" + " {command} CHANGE Botox peer connection reset.by peer\n" + " Renames the bot \"Botox\" to \"peer\" with the given username, hostname, and realname."), + "botserv/bot/change", "command"_kw = source.command); + else if (subcommand.equals_ci("DEL")) + source.Reply(_("\002{command} DEL\002 removes the bot \037nickname\037 from the bot list. The bot will quit from any channels it is in, and will not be replaced." + " This command requires the operator privilege for command \002{0}\002.\n" + "\n" + "Example:\n" + " {command} DEL peer\n" + " Removes the bot \"peer\" from the bot list."), + "botserv/bot/del", "command"_kw = source.command); + else + { + source.Reply(_("Allows Services Operators to create, modify, and delete bots that users will be able to use on their channels.")); + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("\n" + "The \002ADD\002 command adds a bot with the given \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 to the bot list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002CHANGE\002 command allows changing the \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 of bot \037oldnickname\037.\n" + "\002{msg}{service} {help} {command} CHANGE\002 for more information.\n" + "\n" + "The \002{command} DEL\002 removes the bot \037nickname\037 from the bot list.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information."), + "msg"_kw = Config->StrictPrivmsg, "help"_kw = help->cname, "command"_kw = source.command); + } + return true; + } +}; + +class BSBot : public Module +{ + CommandBSBot commandbsbot; + + public: + BSBot(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsbot(this) + { + + } +}; + +MODULE_INIT(BSBot) diff --git a/modules/botserv/botlist.cpp b/modules/botserv/botlist.cpp new file mode 100644 index 000000000..ce899719e --- /dev/null +++ b/modules/botserv/botlist.cpp @@ -0,0 +1,92 @@ +/* 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" + +class CommandBSBotList : public Command +{ + public: + CommandBSBotList(Module *creator) : Command(creator, "botserv/botlist", 0, 0) + { + this->SetDesc(_("Lists available bots")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + unsigned count = 0; + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Nick")).AddColumn(_("Mask")); + + for (BotInfo *bi : Serialize::GetObjects(botinfo)) + { + if (source.HasPriv("botserv/administration") || !bi->GetOperOnly()) + { + ++count; + ListFormatter::ListEntry entry; + entry["Nick"] = (bi->GetOperOnly() ? "* " : "") + bi->GetNick(); + entry["Mask"] = bi->GetUser() + "@" + bi->GetHost(); + list.AddEntry(entry); + } + } + + std::vector replies; + list.Process(replies); + + if (!count) + { + source.Reply(_("There are no bots available")); + return; + } + + source.Reply(_("Bot list:")); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("{0} bots available."), count); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + ServiceBot *bi; + Anope::string name; + Command::FindCommandFromService("botserv/assign", bi, name); + if (!bi) + return false; + + source.Reply(_("Lists all available bots. You may use the \002{msg}{service} {assign}\002 command to assign a bot to your channel." + "The bot names are vanity; they all proviate the same commands and features."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "assign"_kw = name); + if (source.HasPriv("botserv/administration")) + source.Reply(_("Bots prefixed by a * are reserved for Services Operators with the privilege \002{0}\002."), + "botserv/administration"); + source.Reply(_("\n" + "Example:\n" + " {command} BOTLIST"), + "command"_kw = source.command); + return true; + } +}; + +class BSBotList : public Module +{ + CommandBSBotList commandbsbotlist; + + public: + BSBotList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsbotlist(this) + { + + } +}; + +MODULE_INIT(BSBotList) diff --git a/modules/botserv/control.cpp b/modules/botserv/control.cpp new file mode 100644 index 000000000..52c0933c5 --- /dev/null +++ b/modules/botserv/control.cpp @@ -0,0 +1,168 @@ +/* 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" + +class CommandBSSay : public Command +{ + public: + CommandBSSay(Module *creator) : Command(creator, "botserv/say", 2, 2) + { + this->SetDesc(_("Makes the bot say the specified text on the specified channel")); + this->SetSyntax(_("\037channel\037 \037text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &text = params[1]; + + 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("SAY") && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SAY", ci->GetName()); + return; + } + + if (!ci->GetBot()) + { + source.Reply(_("There is no bot assigned to \002{0}\002. One must be assigned to the channel before this command can be used."), ci->GetName()); + ServiceBot *bi; + Anope::string name; + Command::FindCommandFromService("botserv/assign", bi, name); + CommandInfo *help = source.service->FindCommand("generic/help"); + if (bi && help) + source.Reply(_("See \002{msg}{service} {help} {command}\002 for information on assigning bots."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "command"_kw = name); + return; + } + + if (!ci->c || !ci->c->FindUser(ci->GetBot())) + { + source.Reply(_("Bot \002{0}\002 is not on channel \002{1}\002."), ci->GetBot()->nick, ci->GetName()); + return; + } + + if (text[0] == '\001') + { + this->OnSyntaxError(source, ""); + return; + } + + IRCD->SendPrivmsg(ci->GetBot(), ci->GetName(), "%s", text.c_str()); + ci->GetBot()->lastmsg = Anope::CurTime; + + bool override = !source.AccessFor(ci).HasPriv("SAY"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to say: " << text; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Makes the bot say the given \037text\037 on \037channel\037.\n" + "\n" + "Example:\n" + " {command} #anope mmm pie\n" + " Makes the assigned service bot say \"mmm pie\".")); + return true; + } +}; + +class CommandBSAct : public Command +{ + public: + CommandBSAct(Module *creator) : Command(creator, "botserv/act", 2, 2) + { + this->SetDesc(_("Makes the bot do the equivalent of a \"/me\" command")); + this->SetSyntax(_("\037channel\037 \037text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + Anope::string message = params[1]; + + 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("SAY") && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SAY", ci->GetName()); + return; + } + + if (!ci->GetBot()) + { + source.Reply(_("There is no bot assigned to \002{0}\002. One must be assigned to the channel before this command can be used."), ci->GetName()); + ServiceBot *bi; + Anope::string name; + Command::FindCommandFromService("botserv/assign", bi, name); + CommandInfo *help = source.service->FindCommand("generic/help"); + if (bi && help) + source.Reply(_("See \002{msg}{service} {help} {command}\002 for information on assigning bots."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "command"_kw = name); + return; + } + + if (!ci->c || !ci->c->FindUser(ci->GetBot())) + { + source.Reply(_("Bot \002{0}\002 is not on channel \002{1}\002."), ci->GetBot()->nick, ci->GetName()); + return; + } + + message = message.replace_all_cs("\1", ""); + if (message.empty()) + return; + + IRCD->SendAction(ci->GetBot(), ci->GetName(), "%s", message.c_str()); + ci->GetBot()->lastmsg = Anope::CurTime; + + bool override = !source.AccessFor(ci).HasPriv("SAY"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to say: " << message; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Makes the assigned bot do the equivalent of a \"/me\" command on \037channel\037 using the given \037text\037.\n" + "\n" + "Example:\n" + " {command} #anope slaps Cronus\n" + " Shows the assigned service bot \"slapping\" Cronus."), + "command"_kw = source.command); + return true; + } +}; + +class BSControl : public Module +{ + CommandBSSay commandbssay; + CommandBSAct commandbsact; + + public: + BSControl(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbssay(this), commandbsact(this) + { + + } +}; + +MODULE_INIT(BSControl) diff --git a/modules/botserv/info.cpp b/modules/botserv/info.cpp new file mode 100644 index 000000000..6d74c308e --- /dev/null +++ b/modules/botserv/info.cpp @@ -0,0 +1,116 @@ +/* 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 CommandBSInfo : public Command +{ + EventHandlers &onbotinfo; + + public: + CommandBSInfo(Module *creator, EventHandlers &event) : Command(creator, "botserv/info", 1, 1), onbotinfo(event) + { + this->SetSyntax(_("{\037channel\037 | \037nickname\037}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &query = params[0]; + + ServiceBot *bi = ServiceBot::Find(query, true); + ChanServ::Channel *ci = ChanServ::Find(query); + InfoFormatter info(source.nc); + + if (bi) + { + source.Reply(_("Information for bot \002%s\002:"), bi->nick.c_str()); + info[_("Mask")] = bi->GetIdent() + "@" + bi->host; + info[_("Real name")] = bi->realname; + info[_("Created")] = Anope::strftime(bi->bi->GetCreated(), source.GetAccount()); + info[_("Options")] = bi->bi->GetOperOnly() ? _("Private") : _("None"); + info[_("Used on")] = stringify(bi->GetChannelCount()) + " channel(s)"; + + this->onbotinfo(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); + + std::vector replies; + info.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + if (source.HasPriv("botserv/administration")) + { + Anope::string buf; + for (ChanServ::Channel *ci2 : bi->GetChannels()) + buf += " " + ci2->GetName(); + source.Reply(buf); + } + + } + else if (ci) + { + if (!source.AccessFor(ci).HasPriv("INFO") && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "INFO", ci->GetName()); + return; + } + + source.Reply(_("Information for channel \002{0}\002:"), ci->GetName()); + info[_("Bot nick")] = ci->GetBot() ? ci->GetBot()->nick : _("not assigned yet"); + + Anope::string enabled = Language::Translate(source.nc, _("Enabled")); + Anope::string disabled = Language::Translate(source.nc, _("Disabled")); + + this->onbotinfo(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); + + std::vector replies; + info.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + else + source.Reply(_("\002{0}\002 is not a valid bot or registered channel."), query); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to see {0} information about a channel or a bot." + " If the parameter is a channel, then you'll get information such as enabled kickers." + " If the parameter is a bot nickname, you'll get information about a bot, such as creation time and number of channels it is on."), + source.service->nick); + return true; + } + + const Anope::string GetDesc(CommandSource &source) const override + { + return Anope::printf(Language::Translate(source.GetAccount(), _("Allows you to see %s information about a channel or a bot")), source.service->nick.c_str()); + } +}; + +class BSInfo : public Module +{ + CommandBSInfo commandbsinfo; + EventHandlers onbotinfo; + + public: + BSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsinfo(this, onbotinfo) + , onbotinfo(this) + { + + } +}; + +MODULE_INIT(BSInfo) diff --git a/modules/botserv/kick.cpp b/modules/botserv/kick.cpp new file mode 100644 index 000000000..70ed5b2ad --- /dev/null +++ b/modules/botserv/kick.cpp @@ -0,0 +1,1664 @@ +/* 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_kick.h" +#include "modules/bs_badwords.h" +#include "modules/bs_info.h" + +static Module *me; + +enum TTBType +{ + TTB_BOLDS, + TTB_COLORS, + TTB_REVERSES, + TTB_UNDERLINES, + TTB_BADWORDS, + TTB_CAPS, + TTB_FLOOD, + TTB_REPEAT, + TTB_ITALICS, + TTB_AMSGS, + TTB_SIZE +}; + +class KickerDataImpl : public KickerData +{ + public: + KickerDataImpl(Serialize::TypeBase *type) : KickerData(type) { } + KickerDataImpl(Serialize::TypeBase *type, Serialize::ID id) : KickerData(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *) override; + + bool GetAmsgs() override; + void SetAmsgs(const bool &) override; + + bool GetBadwords() override; + void SetBadwords(const bool &) override; + + bool GetBolds() override; + void SetBolds(const bool &) override; + + bool GetCaps() override; + void SetCaps(const bool &) override; + + bool GetColors() override; + void SetColors(const bool &) override; + + bool GetFlood() override; + void SetFlood(const bool &) override; + + bool GetItalics() override; + void SetItalics(const bool &) override; + + bool GetRepeat() override; + void SetRepeat(const bool &) override; + + bool GetReverses() override; + void SetReverses(const bool &) override; + + bool GetUnderlines() override; + void SetUnderlines(const bool &) override; + + int16_t GetTTBBolds() override; + void SetTTBBolds(const int16_t &) override; + + int16_t GetTTBColors() override; + void SetTTBColors(const int16_t &) override; + + int16_t GetTTBReverses() override; + void SetTTBReverses(const int16_t &) override; + + int16_t GetTTBUnderlines() override; + void SetTTBUnderlines(const int16_t &) override; + + int16_t GetTTBBadwords() override; + void SetTTBBadwords(const int16_t &) override; + + int16_t GetTTBCaps() override; + void SetTTBCaps(const int16_t &) override; + + int16_t GetTTBFlood() override; + void SetTTBFlood(const int16_t &) override; + + int16_t GetTTBRepeat() override; + void SetTTBRepeat(const int16_t &) override; + + int16_t GetTTBItalics() override; + void SetTTBItalics(const int16_t &) override; + + int16_t GetTTBAmsgs() override; + void SetTTBAmsgs(const int16_t &) override; + + int16_t GetCapsMin() override; + void SetCapsMin(const int16_t &) override; + + int16_t GetCapsPercent() override; + void SetCapsPercent(const int16_t &) override; + + int16_t GetFloodLines() override; + void SetFloodLines(const int16_t &) override; + + int16_t GetFloodSecs() override; + void SetFloodSecs(const int16_t &) override; + + int16_t GetRepeatTimes() override; + void SetRepeatTimes(const int16_t &) override; + + bool GetDontKickOps() override; + void SetDontKickOps(const bool &) override; + + bool GetDontKickVoices() override; + void SetDontKickVoices(const bool &) override; +}; + +class KickerDataType : public Serialize::Type +{ + public: + Serialize::ObjectField channel; + Serialize::Field amsgs, badwords, bolds, caps, colors, flood, italics, repeat, reverses, underlines; + Serialize::Field ttb_bolds, ttb_colors, ttb_reverses, ttb_underlines, ttb_badwords, ttb_caps, ttb_flood, ttb_repeat, ttb_italics, ttb_amsgs, + capsmin, capspercent, + floodlines, floodsecs, + repeattimes; + Serialize::Field dontkickops, dontkickvoices; + + KickerDataType(Module *owner) : Serialize::Type(owner, "KickerData") + , channel(this, "channel", true) + , amsgs(this, "amsgs") + , badwords(this, "badwords") + , bolds(this, "bolds") + , caps(this, "caps") + , colors(this, "colors") + , flood(this, "flood") + , italics(this, "italics") + , repeat(this, "repeat") + , reverses(this, "reverses") + , underlines(this, "underlines") + , ttb_bolds(this, "ttb_bolds") + , ttb_colors(this, "ttb_colors") + , ttb_reverses(this, "ttb_reverses") + , ttb_underlines(this, "ttb_underlines") + , ttb_badwords(this, "ttb_badwords") + , ttb_caps(this, "ttb_caps") + , ttb_flood(this, "ttb_flood") + , ttb_repeat(this, "ttb_repeat") + , ttb_italics(this, "ttb_italics") + , ttb_amsgs(this, "ttb_amsgs") + , capsmin(this, "capsmin") + , capspercent(this, "capspercent") + , floodlines(this, "floodlines") + , floodsecs(this, "floodsecs") + , repeattimes(this, "repeattimes") + , dontkickops(this, "dontkickops") + , dontkickvoices(this, "dontkickvoices") + { + } +}; + +ChanServ::Channel *KickerDataImpl::GetChannel() +{ + return Get(&KickerDataType::channel); +} + +void KickerDataImpl::SetChannel(ChanServ::Channel *channel) +{ + Set(&KickerDataType::amsgs, channel); +} + +bool KickerDataImpl::GetAmsgs() +{ + return Get(&KickerDataType::amsgs); +} + +void KickerDataImpl::SetAmsgs(const bool &b) +{ + Set(&KickerDataType::amsgs, b); +} + +bool KickerDataImpl::GetBadwords() +{ + return Get(&KickerDataType::badwords); +} + +void KickerDataImpl::SetBadwords(const bool &b) +{ + Set(&KickerDataType::badwords, b); +} + +bool KickerDataImpl::GetBolds() +{ + return Get(&KickerDataType::bolds); +} + +void KickerDataImpl::SetBolds(const bool &b) +{ + Set(&KickerDataType::bolds, b); +} + +bool KickerDataImpl::GetCaps() +{ + return Get(&KickerDataType::caps); +} + +void KickerDataImpl::SetCaps(const bool &b) +{ + Set(&KickerDataType::caps, b); +} + +bool KickerDataImpl::GetColors() +{ + return Get(&KickerDataType::colors); +} + +void KickerDataImpl::SetColors(const bool &b) +{ + Set(&KickerDataType::colors, b); +} + +bool KickerDataImpl::GetFlood() +{ + return Get(&KickerDataType::flood); +} + +void KickerDataImpl::SetFlood(const bool &b) +{ + Set(&KickerDataType::flood, b); +} + +bool KickerDataImpl::GetItalics() +{ + return Get(&KickerDataType::italics); +} + +void KickerDataImpl::SetItalics(const bool &b) +{ + Set(&KickerDataType::italics, b); +} + +bool KickerDataImpl::GetRepeat() +{ + return Get(&KickerDataType::repeat); +} + +void KickerDataImpl::SetRepeat(const bool &b) +{ + Set(&KickerDataType::repeat, b); +} + +bool KickerDataImpl::GetReverses() +{ + return Get(&KickerDataType::reverses); +} + +void KickerDataImpl::SetReverses(const bool &b) +{ + Set(&KickerDataType::reverses, b); +} + +bool KickerDataImpl::GetUnderlines() +{ + return Get(&KickerDataType::underlines); +} + +void KickerDataImpl::SetUnderlines(const bool &b) +{ + Set(&KickerDataType::underlines, b); +} + +int16_t KickerDataImpl::GetTTBBolds() +{ + return Get(&KickerDataType::ttb_bolds); +} + +void KickerDataImpl::SetTTBBolds(const int16_t &i) +{ + Set(&KickerDataType::ttb_bolds, i); +} + +int16_t KickerDataImpl::GetTTBColors() +{ + return Get(&KickerDataType::ttb_colors); +} + +void KickerDataImpl::SetTTBColors(const int16_t &i) +{ + Set(&KickerDataType::ttb_colors, i); +} + +int16_t KickerDataImpl::GetTTBReverses() +{ + return Get(&KickerDataType::ttb_reverses); +} + +void KickerDataImpl::SetTTBReverses(const int16_t &i) +{ + Set(&KickerDataType::ttb_reverses, i); +} + +int16_t KickerDataImpl::GetTTBUnderlines() +{ + return Get(&KickerDataType::ttb_underlines); +} + +void KickerDataImpl::SetTTBUnderlines(const int16_t &i) +{ + Set(&KickerDataType::ttb_underlines, i); +} + +int16_t KickerDataImpl::GetTTBBadwords() +{ + return Get(&KickerDataType::ttb_badwords); +} + +void KickerDataImpl::SetTTBBadwords(const int16_t &i) +{ + Set(&KickerDataType::ttb_badwords, i); +} + +int16_t KickerDataImpl::GetTTBCaps() +{ + return Get(&KickerDataType::ttb_caps); +} + +void KickerDataImpl::SetTTBCaps(const int16_t &i) +{ + Set(&KickerDataType::ttb_caps, i); +} + +int16_t KickerDataImpl::GetTTBFlood() +{ + return Get(&KickerDataType::ttb_flood); +} + +void KickerDataImpl::SetTTBFlood(const int16_t &i) +{ + Set(&KickerDataType::ttb_flood, i); +} + +int16_t KickerDataImpl::GetTTBRepeat() +{ + return Get(&KickerDataType::ttb_repeat); +} + +void KickerDataImpl::SetTTBRepeat(const int16_t &i) +{ + Set(&KickerDataType::ttb_repeat, i); +} + +int16_t KickerDataImpl::GetTTBItalics() +{ + return Get(&KickerDataType::ttb_italics); +} + +void KickerDataImpl::SetTTBItalics(const int16_t &i) +{ + Set(&KickerDataType::ttb_italics, i); +} + +int16_t KickerDataImpl::GetTTBAmsgs() +{ + return Get(&KickerDataType::ttb_amsgs); +} + +void KickerDataImpl::SetTTBAmsgs(const int16_t &i) +{ + Set(&KickerDataType::ttb_amsgs, i); +} + +int16_t KickerDataImpl::GetCapsMin() +{ + return Get(&KickerDataType::capsmin); +} + +void KickerDataImpl::SetCapsMin(const int16_t &i) +{ + Set(&KickerDataType::capsmin, i); +} + +int16_t KickerDataImpl::GetCapsPercent() +{ + return Get(&KickerDataType::capspercent); +} + +void KickerDataImpl::SetCapsPercent(const int16_t &i) +{ + Set(&KickerDataType::capspercent, i); +} + +int16_t KickerDataImpl::GetFloodLines() +{ + return Get(&KickerDataType::floodlines); +} + +void KickerDataImpl::SetFloodLines(const int16_t &i) +{ + Set(&KickerDataType::floodlines, i); +} + +int16_t KickerDataImpl::GetFloodSecs() +{ + return Get(&KickerDataType::floodsecs); +} + +void KickerDataImpl::SetFloodSecs(const int16_t &i) +{ + Set(&KickerDataType::floodsecs, i); +} + +int16_t KickerDataImpl::GetRepeatTimes() +{ + return Get(&KickerDataType::repeattimes); +} + +void KickerDataImpl::SetRepeatTimes(const int16_t &i) +{ + Set(&KickerDataType::repeattimes, i); +} + +bool KickerDataImpl::GetDontKickOps() +{ + return Get(&KickerDataType::dontkickops); +} + +void KickerDataImpl::SetDontKickOps(const bool &b) +{ + Set(&KickerDataType::dontkickops, b); +} + +bool KickerDataImpl::GetDontKickVoices() +{ + return Get(&KickerDataType::dontkickvoices); +} + +void KickerDataImpl::SetDontKickVoices(const bool &b) +{ + Set(&KickerDataType::dontkickvoices, b); +} + +class CommandBSKick : public Command +{ + public: + CommandBSKick(Module *creator) : Command(creator, "botserv/kick", 0) + { + this->SetDesc(_("Configures kickers")); + this->SetSyntax(_("\037option\037 \037channel\037 {\037ON|OFF\037} [\037settings\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Configures bot kickers." + " Use of this command requires the \002SET\002 privilege on \037channel\037.\n" + "\n" + "Available kickers:")); + + Anope::string this_name = source.command; + for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + + if (c_name.find_ci(this_name + " ") == 0) + { + ServiceReference command("Command", info.name); + if (command) + { + source.command = c_name; + command->OnServHelp(source); + } + } + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("See \002{0}{1} {2} {3}