diff options
author | Sadie Powell <sadie@witchery.services> | 2024-03-14 21:18:08 +0000 |
---|---|---|
committer | Sadie Powell <sadie@witchery.services> | 2024-03-14 21:35:17 +0000 |
commit | beaf09de7bcb0042dfd8f388064126e511a5b28d (patch) | |
tree | ba14e1ff1ff96b5623c459b2de35d766d086e427 /modules | |
parent | c8d8978cd0bfb10613da331c5b13bbcf8486f85f (diff) |
Rework sending global notices.
Admins can now queue multiple messages and send them when they are
ready. This is fully compatible with the previous global behaviour.
Admins can now also send messages to individual servers. This is
useful for informing users of maintenance due to downtime.
Diffstat (limited to 'modules')
-rw-r--r-- | modules/global/gl_global.cpp | 73 | ||||
-rw-r--r-- | modules/global/gl_queue.cpp | 208 | ||||
-rw-r--r-- | modules/global/gl_server.cpp | 112 | ||||
-rw-r--r-- | modules/global/global.cpp | 152 | ||||
-rw-r--r-- | modules/operserv/os_defcon.cpp | 12 |
5 files changed, 494 insertions, 63 deletions
diff --git a/modules/global/gl_global.cpp b/modules/global/gl_global.cpp index edfaa21a5..25fd05f08 100644 --- a/modules/global/gl_global.cpp +++ b/modules/global/gl_global.cpp @@ -14,40 +14,75 @@ class CommandGLGlobal final : public Command { - ServiceReference<GlobalService> GService; +private: + ServiceReference<GlobalService> global; + + BotInfo *GetSender(CommandSource &source) + { + Reference<BotInfo> sender; + if (global) + sender = global->GetDefaultSender(); + if (!sender) + sender = source.service; + return sender; + } public: - CommandGLGlobal(Module *creator) : Command(creator, "global/global", 1, 1), GService("GlobalService", "Global") + CommandGLGlobal(Module *creator) + : Command(creator, "global/global", 0) + , global("GlobalService", "Global") { this->SetDesc(_("Send a message to all users")); - this->SetSyntax(_("\037message\037")); + this->SetSyntax(_("[\037message\037]")); } void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override { - const Anope::string &msg = params[0]; + if (!global) + { + source.Reply(SERVICE_UNAVAILABLE, source.service->nick.c_str()); + return; + } + + auto queuesize = global->CountQueue(source.nc); + if (!queuesize && params.empty()) + { + source.Reply(GLOBAL_NO_MESSAGE); + return; + } - if (!GService) - source.Reply("No global reference, is global loaded?"); + if (queuesize && !params.empty()) + { + source.Reply(GLOBAL_QUEUE_CONFLICT); + return; + } + + if (params.empty()) + { + // We are sending the message queue. + global->SendQueue(source, GetSender(source)); + } else { - Log(LOG_ADMIN, source, this); - GService->SendGlobal(NULL, source.GetNick(), msg); + // We are sending a single message. + global->SendSingle(params[0], &source, GetSender(source)); + queuesize = 1; } + + Log(LOG_ADMIN, source, this) << "to send " << queuesize << " messages to all users"; } bool OnHelp(CommandSource &source, const Anope::string &subcommand) override { - Reference<BotInfo> sender; - if (GService) - sender = GService->GetDefaultSender(); - if (!sender) - sender = source.service; - this->SendSyntax(source); source.Reply(" "); - source.Reply(_("Allows Administrators to send messages to all users on the\n" - "network. The message will be sent from the nick \002%s\002."), sender->nick.c_str()); + source.Reply(_( + "Allows sending messages to all users on the network. The message will be sent\n" + "from \002%s\002.\n" + "\n" + "You can either send a message by specifying it as a parameter or provide no\n" + "parameters to send a previously queued message.\n" + ), GetSender(source)->nick.c_str()); return true; } }; @@ -55,11 +90,13 @@ public: class GLGlobal final : public Module { +private: CommandGLGlobal commandglglobal; public: - GLGlobal(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), - commandglglobal(this) + GLGlobal(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, VENDOR) + , commandglglobal(this) { } diff --git a/modules/global/gl_queue.cpp b/modules/global/gl_queue.cpp new file mode 100644 index 000000000..fde15e15d --- /dev/null +++ b/modules/global/gl_queue.cpp @@ -0,0 +1,208 @@ +/* Global core functions + * + * (C) 2003-2024 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" + +#define QUEUE_EMPTY _("You have no messages queued.") + +class QueueDelCallback final + : public NumberList +{ +private: + unsigned deleted = 0; + ServiceReference<GlobalService> &global; + CommandSource &source; + +public: + QueueDelCallback(CommandSource &src, ServiceReference<GlobalService>& gl, const Anope::string &list) + : NumberList(list, true) + , global(gl) + , source(src) + { + } + + ~QueueDelCallback() override + { + switch (deleted) + { + case 0: + source.Reply(_("No matching entries in your message queue.")); + break; + case 1: + source.Reply(_("Deleted one entry from your message queue.")); + break; + default: + source.Reply(_("Deleted %u entries from your message queue."), deleted); + break; + } + } + + void HandleNumber(unsigned number) override + { + if (!number || number > global->CountQueue(source.nc)) + return; + + if (global->Unqueue(source.nc, number - 1)) + deleted++; + } +}; + +class CommandGLQueue final + : public Command +{ +private: + ServiceReference<GlobalService> global; + + void DoAdd(CommandSource &source, const Anope::string &message) + { + if (message.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + auto maxqueue = Config->GetModule(this->module)->Get<size_t>("maxqueue", "10"); + if (global->CountQueue(source.nc) >= maxqueue) + { + source.Reply(_("You can not queue any more messages.")); + return; + } + + global->Queue(source.nc, message); + source.Reply(_("Your message has been queued.")); + Log(LOG_ADMIN, source, this) << "to queue: " << message; + } + + void DoClear(CommandSource &source) + { + if (!global->CountQueue(source.nc)) + { + source.Reply(_("You do not have any queued messages.")); + return; + } + + global->ClearQueue(source.nc); + source.Reply(_("Your message queue has been cleared.")); + Log(LOG_ADMIN, source, this); + } + + void DoDel(CommandSource &source, const Anope::string &what) + { + if (what.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (!global->CountQueue(source.nc)) + { + source.Reply(QUEUE_EMPTY); + return; + } + + QueueDelCallback(source, global, what).Process(); + } + + void DoList(CommandSource &source) + { + const auto *q = global->GetQueue(source.nc); + if (!q || q->empty()) + { + source.Reply(QUEUE_EMPTY); + return; + } + + ListFormatter list(source.nc); + list.AddColumn(_("Number")).AddColumn(_("Message")); + for (size_t i = 0; i < q->size(); ++i) + { + ListFormatter::ListEntry entry; + entry["Number"] = Anope::ToString(i + 1); + entry["Message"] = (*q)[i]; + list.AddEntry(entry); + } + + std::vector<Anope::string> replies; + list.Process(replies); + for (const auto &reply : replies) + source.Reply(reply); + } + +public: + CommandGLQueue(Module *creator) + : Command(creator, "global/queue", 1, 2) + , global("GlobalService", "Global") + { + this->SetDesc(_("Manages your pending message queue.")); + this->SetSyntax(_("ADD \037message\037")); + this->SetSyntax(_("DEL \037entry-num\037")); + this->SetSyntax("LIST"); + this->SetSyntax("CLEAR"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!global) + { + source.Reply(SERVICE_UNAVAILABLE, source.service->nick.c_str()); + return; + } + + const auto &cmd = params[0]; + const auto &what = params.size() > 1 ? params[1] : ""; + if (cmd.equals_ci("ADD")) + this->DoAdd(source, what); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source); + else if (cmd.equals_ci("DEL")) + this->DoDel(source, what); + else if (cmd.equals_ci("LIST")) + this->DoList(source); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(""); + source.Reply(_( + "Allows queueing messages to send to users on the network.\n" + "\n" + "The \002QUEUE ADD\002 command adds the given message to the message queue." + "\n" + "The \002QUEUE CLEAR\002 command clears the message queue." + "\n" + "The \002QUEUE DEL\002 command removes the specified message from the message queue. The\n" + "message number can be obtained from the output of the \002QUEUE LIST\002 command." + "\n" + "The \002QUEUE LIST\002 command lists all messages that are currently in the message queue." + )); + return true; + } +}; + +class GLQueue final + : public Module +{ +private: + CommandGLQueue commandglqueue; + +public: + GLQueue(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, VENDOR) + , commandglqueue(this) + { + + } +}; + +MODULE_INIT(GLQueue) diff --git a/modules/global/gl_server.cpp b/modules/global/gl_server.cpp new file mode 100644 index 000000000..d1f1cffe2 --- /dev/null +++ b/modules/global/gl_server.cpp @@ -0,0 +1,112 @@ +/* Global core functions + * + * (C) 2003-2024 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 CommandGLServer final + : public Command +{ +private: + ServiceReference<GlobalService> global; + + BotInfo *GetSender(CommandSource &source) + { + Reference<BotInfo> sender; + if (global) + sender = global->GetDefaultSender(); + if (!sender) + sender = source.service; + return sender; + } + +public: + CommandGLServer(Module *creator) + : Command(creator, "global/server", 1) + , global("GlobalService", "Global") + { + this->SetDesc(_("Send a message to all users on a server")); + this->SetSyntax(_("\037server\037 [\037message\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!global) + { + source.Reply(SERVICE_UNAVAILABLE, source.service->nick.c_str()); + return; + } + + auto *server = Server::Find(params[0]); + if (!server || server == Me || server->IsJuped()) + { + source.Reply(_("Server \002%s\002 is not linked to the network."), params[0].c_str()); + return; + } + + auto queuesize = global->CountQueue(source.nc); + if (!queuesize && params.size() < 2) + { + source.Reply(GLOBAL_NO_MESSAGE); + return; + } + + if (queuesize && params.size() > 1) + { + source.Reply(GLOBAL_QUEUE_CONFLICT); + return; + } + + if (params.empty()) + { + // We are sending the message queue. + global->SendQueue(source, GetSender(source), server); + } + else + { + // We are sending a single message. + global->SendSingle(params[1], &source, GetSender(source), server); + queuesize = 1; + } + + Log(LOG_ADMIN, source, this) << "to send " << queuesize << " messages to users on " << server->GetName(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_( + "Allows sending messages to all users on a server. The message will be sent\n" + "from \002%s\002.\n" + "\n" + "You can either send a message by specifying it as a parameter or provide no\n" + "parameters to send a previously queued message.\n" + ), GetSender(source)->nick.c_str()); + return true; + } +}; + +class GLServer final + : public Module +{ +private: + CommandGLServer commandglserver; + +public: + GLServer(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, VENDOR) + , commandglserver(this) + { + + } +}; + +MODULE_INIT(GLServer) diff --git a/modules/global/global.cpp b/modules/global/global.cpp index a5288d754..35177dcfd 100644 --- a/modules/global/global.cpp +++ b/modules/global/global.cpp @@ -15,88 +15,162 @@ class GlobalCore final : public Module , public GlobalService { - Reference<BotInfo> Global; +private: + Reference<BotInfo> global; + PrimitiveExtensibleItem<std::vector<Anope::string>> queue; - void ServerGlobal(BotInfo *sender, Server *s, const Anope::string &message) + void ServerGlobal(BotInfo *sender, Server *server, bool children, const Anope::string &message) { - if (s != Me && !s->IsJuped()) - s->Notice(sender, message); - for (auto *link : s->GetLinks()) - this->ServerGlobal(sender, link, message); + if (server != Me && !server->IsJuped()) + server->Notice(sender, message); + + if (children) + { + for (auto *link : server->GetLinks()) + this->ServerGlobal(sender, link, true, message); + } } public: - GlobalCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR), - GlobalService(this) + GlobalCore(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , GlobalService(this) + , queue(this, "global-queue") { } - Reference<BotInfo> GetDefaultSender() override + void ClearQueue(NickCore *nc) override { - return Global; + queue.Unset(nc); } - void SendGlobal(BotInfo *sender, const Anope::string &source, const Anope::string &message) override + Reference<BotInfo> GetDefaultSender() const override { - if (Me->GetLinks().empty()) - return; - if (!sender) - sender = Global; - if (!sender) - return; - - Anope::string rmessage; + return global; + } - if (!source.empty() && !Config->GetModule("global")->Get<bool>("anonymousglobal")) - rmessage = "[" + source + "] " + message; - else - rmessage = message; + const std::vector<Anope::string> *GetQueue(NickCore* nc) const override + { + return queue.Get(nc); + } - this->ServerGlobal(sender, Servers::GetUplink(), rmessage); + size_t Queue(NickCore *nc, const Anope::string &message) override + { + auto *q = queue.Require(nc); + q->push_back(message); + return q->size(); } void OnReload(Configuration::Conf *conf) override { - const Anope::string &glnick = conf->GetModule(this)->Get<const Anope::string>("client"); - + const auto glnick = conf->GetModule(this)->Get<const Anope::string>("client"); if (glnick.empty()) throw ConfigException(Module::name + ": <client> must be defined"); - BotInfo *bi = BotInfo::Find(glnick, true); + auto *bi = BotInfo::Find(glnick, true); if (!bi) throw ConfigException(Module::name + ": no bot named " + glnick); - Global = bi; + global = bi; } void OnRestart() override { - const Anope::string &gl = Config->GetModule(this)->Get<const Anope::string>("globaloncycledown"); - if (!gl.empty()) - this->SendGlobal(Global, "", gl); + const auto msg = Config->GetModule(this)->Get<const Anope::string>("globaloncycledown"); + if (!msg.empty()) + this->SendSingle(msg, nullptr, nullptr, nullptr); } void OnShutdown() override { - const Anope::string &gl = Config->GetModule(this)->Get<const Anope::string>("globaloncycledown"); - if (!gl.empty()) - this->SendGlobal(Global, "", gl); + const auto msg = Config->GetModule(this)->Get<const Anope::string>("globaloncycledown"); + if (!msg.empty()) + this->SendSingle(msg, nullptr, nullptr, nullptr); } void OnNewServer(Server *s) override { - const Anope::string &gl = Config->GetModule(this)->Get<const Anope::string>("globaloncycleup"); - if (!gl.empty() && !Me->IsSynced()) - s->Notice(Global, gl); + const auto msg = Config->GetModule(this)->Get<const Anope::string>("globaloncycleup"); + if (!msg.empty() && !Me->IsSynced()) + s->Notice(global, msg); } EventReturn OnPreHelp(CommandSource &source, const std::vector<Anope::string> ¶ms) override { - if (!params.empty() || source.c || source.service != *Global) + if (!params.empty() || source.c || source.service != *global) return EVENT_CONTINUE; - source.Reply(_("%s commands:"), Global->nick.c_str()); + + source.Reply(_("%s commands:"), global->nick.c_str()); return EVENT_CONTINUE; } + + bool SendQueue(CommandSource &source, BotInfo *sender, Server *server) override + { + // We MUST have an account. + if (!source.nc) + return false; + + // We MUST have a message queue. + auto *q = queue.Get(source.nc); + if (!q || q->empty()) + return false; + + auto success = true; + for (const auto &message : *q) + { + if (!SendSingle(message, &source, sender, server)) + { + success = false; + break; + } + } + + queue.Unset(source.nc); + return success; + } + + bool SendSingle(const Anope::string &message, CommandSource *source, BotInfo* sender, Server* server) override + { + // We MUST have a sender. + if (sender) + sender = global; + if (!sender) + return false; + + if (!server) + server = Servers::GetUplink(); + + Anope::string line; + if (source && !Config->GetModule(this)->Get<bool>("anonymousglobal")) + { + // A source is available and they're not anonymous. + line = Anope::printf("[%s] %s", source->GetNick().c_str(), message.c_str()); + } + else + { + // A source isn't available or they're anonymous. + line = message.empty() ? " " : message; + } + + if (server) + this->ServerGlobal(sender, Servers::GetUplink(), true, line); + else + this->ServerGlobal(sender, server, false, line); + return true; + } + + bool Unqueue(NickCore *nc, size_t idx) + { + auto *q = queue.Get(nc); + if (!q || idx > q->size()) + return false; + + q->erase(q->begin() + idx); + if (q->empty()) + queue.Unset(nc); + + return true; + } }; MODULE_INIT(GlobalCore) diff --git a/modules/operserv/os_defcon.cpp b/modules/operserv/os_defcon.cpp index 564cfec50..b098a4775 100644 --- a/modules/operserv/os_defcon.cpp +++ b/modules/operserv/os_defcon.cpp @@ -134,12 +134,12 @@ public: if (DConfig.globalondefcon) { if (!DConfig.offmessage.empty()) - GlobalService->SendGlobal(NULL, "", DConfig.offmessage); + GlobalService->SendSingle(DConfig.offmessage); else - GlobalService->SendGlobal(NULL, "", Anope::printf(Language::Translate(_("The Defcon level is now at: \002%d\002")), DConfig.defaultlevel)); + GlobalService->SendSingle(Anope::printf(Language::Translate(_("The Defcon level is now at: \002%d\002")), DConfig.defaultlevel)); if (!DConfig.message.empty()) - GlobalService->SendGlobal(NULL, "", DConfig.message); + GlobalService->SendSingle(DConfig.message); } runDefCon(); @@ -217,12 +217,12 @@ public: if (DConfig.globalondefcon) { if (DConfig.defaultlevel == 5 && !DConfig.offmessage.empty()) - GlobalService->SendGlobal(NULL, "", DConfig.offmessage); + GlobalService->SendSingle(DConfig.offmessage); else if (DConfig.defaultlevel != 5) { - GlobalService->SendGlobal(NULL, "", Anope::printf(_("The Defcon level is now at: \002%d\002"), DConfig.defaultlevel)); + GlobalService->SendSingle(Anope::printf(_("The Defcon level is now at: \002%d\002"), DConfig.defaultlevel)); if (!DConfig.message.empty()) - GlobalService->SendGlobal(NULL, "", DConfig.message); + GlobalService->SendSingle(DConfig.message); } } |