From beaf09de7bcb0042dfd8f388064126e511a5b28d Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Thu, 14 Mar 2024 21:18:08 +0000 Subject: [PATCH] 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. --- data/anope.example.conf | 2 +- data/global.example.conf | 26 ++ include/language.h | 1 + include/modules/pseudoclients/global.h | 61 ++- language/anope.en_US.po | 544 +++++++++++++++---------- modules/global/gl_global.cpp | 73 +++- modules/global/gl_queue.cpp | 208 ++++++++++ modules/global/gl_server.cpp | 112 +++++ modules/global/global.cpp | 152 +++++-- modules/operserv/os_defcon.cpp | 12 +- 10 files changed, 895 insertions(+), 296 deletions(-) create mode 100644 modules/global/gl_queue.cpp create mode 100644 modules/global/gl_server.cpp diff --git a/data/anope.example.conf b/data/anope.example.conf index 80302a88f..6198c9776 100644 --- a/data/anope.example.conf +++ b/data/anope.example.conf @@ -789,7 +789,7 @@ log * * hostserv/set hostserv/del hostserv/list * - * global/global + * global/global global/queue global/server * * operserv/news operserv/stats operserv/kick operserv/exception operserv/seen * operserv/mode operserv/session operserv/modinfo operserv/ignore operserv/chanlist diff --git a/data/global.example.conf b/data/global.example.conf index d48fe0581..5b99f0178 100644 --- a/data/global.example.conf +++ b/data/global.example.conf @@ -113,3 +113,29 @@ command { service = "Global"; name = "HELP"; command = "generic/help"; } */ module { name = "gl_global" } command { service = "Global"; name = "GLOBAL"; command = "global/global"; permission = "global/global"; } + +/* + * gl_queue + * + * Provides the command global/queue. + * + * Used for queuing messages to send to every online user. + */ +module +{ + name = "gl_queue" + + /* The maximum number of messages in a message queue. Defaults to 10. */ + maxqueue = 10 +} +command { service = "Global"; name = "QUEUE"; command = "global/queue"; permission = "global/queue"; } + +/* + * gl_server + * + * Provides the command global/server. + * + * Used for sending a message to every online user on a server. + */ +module { name = "gl_server" } +command { service = "Global"; name = "SERVER"; command = "global/server"; permission = "global/server"; } diff --git a/include/language.h b/include/language.h index 405102c1d..657f3669c 100644 --- a/include/language.h +++ b/include/language.h @@ -66,6 +66,7 @@ namespace Language /* Commonly used language strings */ #define CONFIRM_DROP _("Please confirm that you want to drop \002%s\002 with \002%s%s DROP %s %s\002") +#define SERVICE_UNAVAILABLE _("Sorry, %s is temporarily unavailable.") #define MORE_INFO _("\002%s%s HELP %s\002 for more information.") #define BAD_USERHOST_MASK _("Mask must be in the form \037user\037@\037host\037.") #define BAD_EXPIRY_TIME _("Invalid expiry time.") diff --git a/include/modules/pseudoclients/global.h b/include/modules/pseudoclients/global.h index 73283605d..4b5dd0eb4 100644 --- a/include/modules/pseudoclients/global.h +++ b/include/modules/pseudoclients/global.h @@ -8,21 +8,68 @@ #pragma once +#define GLOBAL_NO_MESSAGE _("You do not have any messages queued and did not specify a message to send.") +#define GLOBAL_QUEUE_CONFLICT _("You can not send a single message while you have messages queued.") + class GlobalService : public Service { public: - GlobalService(Module *m) : Service(m, "GlobalService", "Global") + GlobalService(Module *m) + : Service(m, "GlobalService", "Global") { } /** Retrieves the bot which sends global messages unless otherwise specified. */ - virtual Reference GetDefaultSender() = 0; + virtual Reference GetDefaultSender() const = 0; - /** Send out a global message to all users - * @param sender Our client which should send the global - * @param source The sender of the global - * @param message The message + /** Clears any queued messages for the specified account. + * @param nc The account to clear queued messages for. */ - virtual void SendGlobal(BotInfo *sender, const Anope::string &source, const Anope::string &message) = 0; + virtual void ClearQueue(NickCore *nc) = 0; + + /** Retrieves the size of the messages queue for the specified user. + * @param nc The account to count queued messages for. + */ + inline size_t CountQueue(NickCore* nc) const + { + auto *q = GetQueue(nc); + return q ? q->size() : 0; + } + + /** Retrieves the messages queue for the specified user. + * @param nc The account to retrieve queued messages for. + */ + virtual const std::vector *GetQueue(NickCore* nc) const = 0; + + /** Queues a message to be sent later. + * @param nc The account to queue the message for. + * @param message The message to queue. + * @return The new number of messages in the queue. + */ + virtual size_t Queue(NickCore *nc, const Anope::string &message) = 0; + + /** Sends a single message to all users on the network. + * @param message The message to send. + * @param source If non-nullptr then the source of the message. + * @param sender If non-nullptr then the bot to send the message from. + * @param server If non-nullptr then the server to send messages to. + * @return If the message was sent then true; otherwise, false. + */ + virtual bool SendSingle(const Anope::string &message, CommandSource *source = nullptr, BotInfo *sender = nullptr, Server *server = nullptr) = 0; + + /** Sends a message queue to all users on the network. + * @param source The source of the message. + * @param sender If non-nullptr then the bot to send the message from. + * @param server If non-nullptr then the server to send messages to. + * @return If the message queue was sent then true; otherwise, false. + */ + virtual bool SendQueue(CommandSource &source, BotInfo *sender = nullptr, Server *server = nullptr) = 0; + + /** Unqueues a message from the message queue. + * @param nc The account to unqueue the message from. + * @param idx The index of the item to remove. + * @return Whether the message was removed from the queue. + */ + virtual bool Unqueue(NickCore *nc, size_t idx) = 0; }; diff --git a/language/anope.en_US.po b/language/anope.en_US.po index 6bbaf6ffd..4c34b0b35 100644 --- a/language/anope.en_US.po +++ b/language/anope.en_US.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Anope\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-12 19:17+0000\n" -"PO-Revision-Date: 2024-03-12 19:17+0000\n" +"POT-Creation-Date: 2024-03-14 21:31+0000\n" +"PO-Revision-Date: 2024-03-14 21:31+0000\n" "Last-Translator: Sadie Powell \n" "Language-Team: English\n" "Language: en_US\n" @@ -754,6 +754,9 @@ msgstr "pattern [SUSPENDED] [NOEXPIRE]" msgid "pattern [SUSPENDED] [NOEXPIRE] [UNCONFIRMED]" msgstr "pattern [SUSPENDED] [NOEXPIRE] [UNCONFIRMED]" +msgid "server [message]" +msgstr "server [message]" + msgid "server [reason]" msgstr "server [reason]" @@ -1714,6 +1717,9 @@ msgstr "A vHost must be in the format of a valid hostname." msgid "ADD expiry {nick|mask} [reason]" msgstr "ADD expiry {nick|mask} [reason]" +msgid "ADD message" +msgstr "ADD message" + msgid "ADD oper type" msgstr "ADD oper type" @@ -2029,235 +2035,272 @@ msgstr "Allowed to use the MODE command" msgid "Allowed to view the access list" msgstr "Allowed to view the access list" +msgid "" +"Allows Services Operators to change modes for any channel.\n" +"Parameters are the same as for the standard /MODE command.\n" +"Alternatively, CLEAR may be given to clear all modes on the channel.\n" +"If CLEAR ALL is given then all modes, including user status, is removed." +msgstr "" +"Allows Services Operators to change modes for any channel.\n" +"Parameters are the same as for the standard /MODE command.\n" +"Alternatively, CLEAR may be given to clear all modes on the channel.\n" +"If CLEAR ALL is given then all modes, including user status, is removed." + +msgid "" +"Allows Services Operators to change modes for any user.\n" +"Parameters are the same as for the standard /MODE command." +msgstr "" +"Allows Services Operators to change modes for any user.\n" +"Parameters are the same as for the standard /MODE command." + +msgid "" +"Allows Services Operators to create, modify, and delete\n" +"bots that users will be able to use on their own\n" +"channels.\n" +" \n" +"BOT ADD adds a bot with the given nickname, username,\n" +"hostname and realname. Since no integrity checks are done\n" +"for these settings, be really careful.\n" +" \n" +"BOT CHANGE allows you to change the nickname, username, hostname\n" +"or realname of a bot without deleting it (and\n" +"all the data associated with it).\n" +" \n" +"BOT DEL removes the given bot from the bot list.\n" +" \n" +"Note: You cannot create a bot with a nick that is\n" +"currently registered. If an unregistered user is currently\n" +"using the nick, they will be killed." +msgstr "" +"Allows Services Operators to create, modify, and delete\n" +"bots that users will be able to use on their own\n" +"channels.\n" +" \n" +"BOT ADD adds a bot with the given nickname, username,\n" +"hostname and realname. Since no integrity checks are done\n" +"for these settings, be really careful.\n" +" \n" +"BOT CHANGE allows you to change the nickname, username, hostname\n" +"or realname of a bot without deleting it (and\n" +"all the data associated with it).\n" +" \n" +"BOT DEL removes the given bot from the bot list.\n" +" \n" +"Note: You cannot create a bot with a nick that is\n" +"currently registered. If an unregistered user is currently\n" +"using the nick, they will be killed." + +msgid "" +"Allows Services Operators to make services ignore a nick or mask\n" +"for a certain time or until the next restart. The default\n" +"time format is seconds. You can specify it by using units.\n" +"Valid units are: s for seconds, m for minutes,\n" +"h for hours and d for days.\n" +"Combinations of these units are not permitted.\n" +"To make services permanently ignore the user, type 0 as time.\n" +"When adding a mask, it should be in the format nick!user@host,\n" +"everything else will be considered a nick. Wildcards are permitted.\n" +" \n" +"Ignores will not be enforced on IRC Operators." +msgstr "" +"Allows Services Operators to make services ignore a nick or mask\n" +"for a certain time or until the next restart. The default\n" +"time format is seconds. You can specify it by using units.\n" +"Valid units are: s for seconds, m for minutes,\n" +"h for hours and d for days.\n" +"Combinations of these units are not permitted.\n" +"To make services permanently ignore the user, type 0 as time.\n" +"When adding a mask, it should be in the format nick!user@host,\n" +"everything else will be considered a nick. Wildcards are permitted.\n" +" \n" +"Ignores will not be enforced on IRC Operators." + +msgid "" +"Allows Services Operators to manipulate the AKILL list. If\n" +"a user matching an AKILL mask attempts to connect, services\n" +"will issue a KILL for that user and, on supported server\n" +"types, will instruct all servers to add a ban for the mask\n" +"which the user matched.\n" +" \n" +"AKILL ADD adds the given mask to the AKILL\n" +"list for the given reason, which must be given.\n" +"Mask should be in the format of nick!user@host#real name,\n" +"though all that is required is user@host. If a real name is specified,\n" +"the reason must be prepended with a :.\n" +"expiry is specified as an integer followed by one of d\n" +"(days), h (hours), or m (minutes). Combinations (such as\n" +"1h30m) are not permitted. If a unit specifier is not\n" +"included, the default is days (so +30 by itself means 30\n" +"days). To add an AKILL which does not expire, use +0. If the\n" +"usermask to be added starts with a +, an expiry time must\n" +"be given, even if it is the same as the default. The\n" +"current AKILL default expiry time can be found with the\n" +"STATS AKILL command." +msgstr "" +"Allows Services Operators to manipulate the AKILL list. If\n" +"a user matching an AKILL mask attempts to connect, services\n" +"will issue a KILL for that user and, on supported server\n" +"types, will instruct all servers to add a ban for the mask\n" +"which the user matched.\n" +" \n" +"AKILL ADD adds the given mask to the AKILL\n" +"list for the given reason, which must be given.\n" +"Mask should be in the format of nick!user@host#real name,\n" +"though all that is required is user@host. If a real name is specified,\n" +"the reason must be prepended with a :.\n" +"expiry is specified as an integer followed by one of d\n" +"(days), h (hours), or m (minutes). Combinations (such as\n" +"1h30m) are not permitted. If a unit specifier is not\n" +"included, the default is days (so +30 by itself means 30\n" +"days). To add an AKILL which does not expire, use +0. If the\n" +"usermask to be added starts with a +, an expiry time must\n" +"be given, even if it is the same as the default. The\n" +"current AKILL default expiry time can be found with the\n" +"STATS AKILL command." + +msgid "" +"Allows Services Operators to manipulate the SNLINE list. If\n" +"a user with a realname matching an SNLINE mask attempts to\n" +"connect, services will not allow them to pursue their IRC\n" +"session." +msgstr "" +"Allows Services Operators to manipulate the SNLINE list. If\n" +"a user with a realname matching an SNLINE mask attempts to\n" +"connect, services will not allow them to pursue their IRC\n" +"session." + +msgid "" +"Allows Services Operators to manipulate the SQLINE list. If\n" +"a user with a nick matching an SQLINE mask attempts to\n" +"connect, services will not allow them to pursue their IRC\n" +"session.\n" +"If the first character of the mask is #, services will\n" +"prevent the use of matching channels. If the mask is a\n" +"regular expression, the expression will be matched against\n" +"channels too." +msgstr "" +"Allows Services Operators to manipulate the SQLINE list. If\n" +"a user with a nick matching an SQLINE mask attempts to\n" +"connect, services will not allow them to pursue their IRC\n" +"session.\n" +"If the first character of the mask is #, services will\n" +"prevent the use of matching channels. If the mask is a\n" +"regular expression, the expression will be matched against\n" +"channels too." + +msgid "" +"Allows Services Operators to manipulate the list of hosts that\n" +"have specific session limits - allowing certain machines,\n" +"such as shell servers, to carry more than the default number\n" +"of clients at a time. Once a host reaches its session limit,\n" +"all clients attempting to connect from that host will be\n" +"killed. Before the user is killed, they are notified, of a\n" +"source of help regarding session limiting. The content of\n" +"this notice is a config setting." +msgstr "" +"Allows Services Operators to manipulate the list of hosts that\n" +"have specific session limits - allowing certain machines,\n" +"such as shell servers, to carry more than the default number\n" +"of clients at a time. Once a host reaches its session limit,\n" +"all clients attempting to connect from that host will be\n" +"killed. Before the user is killed, they are notified, of a\n" +"source of help regarding session limiting. The content of\n" +"this notice is a config setting." + +msgid "" +"Allows Services Operators to view the session list.\n" +" \n" +"SESSION LIST lists hosts with at least threshold sessions.\n" +"The threshold must be a number greater than 1. This is to\n" +"prevent accidental listing of the large number of single\n" +"session hosts.\n" +" \n" +"SESSION VIEW displays detailed information about a specific\n" +"host - including the current session count and session limit.\n" +"The host value may not include wildcards.\n" +" \n" +"See the EXCEPTION help for more information about session\n" +"limiting and how to set session limits specific to certain\n" +"hosts and groups thereof." +msgstr "" +"Allows Services Operators to view the session list.\n" +" \n" +"SESSION LIST lists hosts with at least threshold sessions.\n" +"The threshold must be a number greater than 1. This is to\n" +"prevent accidental listing of the large number of single\n" +"session hosts.\n" +" \n" +"SESSION VIEW displays detailed information about a specific\n" +"host - including the current session count and session limit.\n" +"The host value may not include wildcards.\n" +" \n" +"See the EXCEPTION help for more information about session\n" +"limiting and how to set session limits specific to certain\n" +"hosts and groups thereof." + +msgid "" +"Allows manipulating the topic of the specified channel.\n" +"The SET command changes the topic of the channel to the given topic\n" +"or unsets the topic if no topic is given. The APPEND command appends\n" +"the given topic to the existing topic.\n" +" \n" +"LOCK and UNLOCK may be used to enable and disable topic lock. When\n" +"topic lock is set, the channel topic will be unchangeable by users who do not have\n" +"the TOPIC privilege." +msgstr "" +"Allows manipulating the topic of the specified channel.\n" +"The SET command changes the topic of the channel to the given topic\n" +"or unsets the topic if no topic is given. The APPEND command appends\n" +"the given topic to the existing topic.\n" +" \n" +"LOCK and UNLOCK may be used to enable and disable topic lock. When\n" +"topic lock is set, the channel topic will be unchangeable by users who do not have\n" +"the TOPIC privilege." + +msgid "" +"Allows queueing messages to send to users on the network.\n" +"\n" +"The QUEUE ADD command adds the given message to the message queue.\n" +"The QUEUE CLEAR command clears the message queue.\n" +"The QUEUE DEL command removes the specified message from the message queue. The\n" +"message number can be obtained from the output of the QUEUE LIST command.\n" +"The QUEUE LIST command lists all messages that are currently in the message queue." +msgstr "" +"Allows queueing messages to send to users on the network.\n" +"\n" +"The QUEUE ADD command adds the given message to the message queue.\n" +"The QUEUE CLEAR command clears the message queue.\n" +"The QUEUE DEL command removes the specified message from the message queue. The\n" +"message number can be obtained from the output of the QUEUE LIST command.\n" +"The QUEUE LIST command lists all messages that are currently in the message queue." + #, c-format msgid "" -"Allows Administrators to send messages to all users on the\n" -"network. The message will be sent from the nick %s." +"Allows sending messages to all users on a server. The message will be sent\n" +"from %s.\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" msgstr "" -"Allows Administrators to send messages to all users on the\n" -"network. The message will be sent from the nick %s." +"Allows sending messages to all users on a server. The message will be sent\n" +"from %s.\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" +#, c-format msgid "" -"Allows Services Operators to change modes for any channel.\n" -"Parameters are the same as for the standard /MODE command.\n" -"Alternatively, CLEAR may be given to clear all modes on the channel.\n" -"If CLEAR ALL is given then all modes, including user status, is removed." +"Allows sending messages to all users on the network. The message will be sent\n" +"from %s.\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" msgstr "" -"Allows Services Operators to change modes for any channel.\n" -"Parameters are the same as for the standard /MODE command.\n" -"Alternatively, CLEAR may be given to clear all modes on the channel.\n" -"If CLEAR ALL is given then all modes, including user status, is removed." - -msgid "" -"Allows Services Operators to change modes for any user.\n" -"Parameters are the same as for the standard /MODE command." -msgstr "" -"Allows Services Operators to change modes for any user.\n" -"Parameters are the same as for the standard /MODE command." - -msgid "" -"Allows Services Operators to create, modify, and delete\n" -"bots that users will be able to use on their own\n" -"channels.\n" -" \n" -"BOT ADD adds a bot with the given nickname, username,\n" -"hostname and realname. Since no integrity checks are done\n" -"for these settings, be really careful.\n" -" \n" -"BOT CHANGE allows you to change the nickname, username, hostname\n" -"or realname of a bot without deleting it (and\n" -"all the data associated with it).\n" -" \n" -"BOT DEL removes the given bot from the bot list.\n" -" \n" -"Note: You cannot create a bot with a nick that is\n" -"currently registered. If an unregistered user is currently\n" -"using the nick, they will be killed." -msgstr "" -"Allows Services Operators to create, modify, and delete\n" -"bots that users will be able to use on their own\n" -"channels.\n" -" \n" -"BOT ADD adds a bot with the given nickname, username,\n" -"hostname and realname. Since no integrity checks are done\n" -"for these settings, be really careful.\n" -" \n" -"BOT CHANGE allows you to change the nickname, username, hostname\n" -"or realname of a bot without deleting it (and\n" -"all the data associated with it).\n" -" \n" -"BOT DEL removes the given bot from the bot list.\n" -" \n" -"Note: You cannot create a bot with a nick that is\n" -"currently registered. If an unregistered user is currently\n" -"using the nick, they will be killed." - -msgid "" -"Allows Services Operators to make services ignore a nick or mask\n" -"for a certain time or until the next restart. The default\n" -"time format is seconds. You can specify it by using units.\n" -"Valid units are: s for seconds, m for minutes,\n" -"h for hours and d for days.\n" -"Combinations of these units are not permitted.\n" -"To make services permanently ignore the user, type 0 as time.\n" -"When adding a mask, it should be in the format nick!user@host,\n" -"everything else will be considered a nick. Wildcards are permitted.\n" -" \n" -"Ignores will not be enforced on IRC Operators." -msgstr "" -"Allows Services Operators to make services ignore a nick or mask\n" -"for a certain time or until the next restart. The default\n" -"time format is seconds. You can specify it by using units.\n" -"Valid units are: s for seconds, m for minutes,\n" -"h for hours and d for days.\n" -"Combinations of these units are not permitted.\n" -"To make services permanently ignore the user, type 0 as time.\n" -"When adding a mask, it should be in the format nick!user@host,\n" -"everything else will be considered a nick. Wildcards are permitted.\n" -" \n" -"Ignores will not be enforced on IRC Operators." - -msgid "" -"Allows Services Operators to manipulate the AKILL list. If\n" -"a user matching an AKILL mask attempts to connect, services\n" -"will issue a KILL for that user and, on supported server\n" -"types, will instruct all servers to add a ban for the mask\n" -"which the user matched.\n" -" \n" -"AKILL ADD adds the given mask to the AKILL\n" -"list for the given reason, which must be given.\n" -"Mask should be in the format of nick!user@host#real name,\n" -"though all that is required is user@host. If a real name is specified,\n" -"the reason must be prepended with a :.\n" -"expiry is specified as an integer followed by one of d\n" -"(days), h (hours), or m (minutes). Combinations (such as\n" -"1h30m) are not permitted. If a unit specifier is not\n" -"included, the default is days (so +30 by itself means 30\n" -"days). To add an AKILL which does not expire, use +0. If the\n" -"usermask to be added starts with a +, an expiry time must\n" -"be given, even if it is the same as the default. The\n" -"current AKILL default expiry time can be found with the\n" -"STATS AKILL command." -msgstr "" -"Allows Services Operators to manipulate the AKILL list. If\n" -"a user matching an AKILL mask attempts to connect, services\n" -"will issue a KILL for that user and, on supported server\n" -"types, will instruct all servers to add a ban for the mask\n" -"which the user matched.\n" -" \n" -"AKILL ADD adds the given mask to the AKILL\n" -"list for the given reason, which must be given.\n" -"Mask should be in the format of nick!user@host#real name,\n" -"though all that is required is user@host. If a real name is specified,\n" -"the reason must be prepended with a :.\n" -"expiry is specified as an integer followed by one of d\n" -"(days), h (hours), or m (minutes). Combinations (such as\n" -"1h30m) are not permitted. If a unit specifier is not\n" -"included, the default is days (so +30 by itself means 30\n" -"days). To add an AKILL which does not expire, use +0. If the\n" -"usermask to be added starts with a +, an expiry time must\n" -"be given, even if it is the same as the default. The\n" -"current AKILL default expiry time can be found with the\n" -"STATS AKILL command." - -msgid "" -"Allows Services Operators to manipulate the SNLINE list. If\n" -"a user with a realname matching an SNLINE mask attempts to\n" -"connect, services will not allow them to pursue their IRC\n" -"session." -msgstr "" -"Allows Services Operators to manipulate the SNLINE list. If\n" -"a user with a realname matching an SNLINE mask attempts to\n" -"connect, services will not allow them to pursue their IRC\n" -"session." - -msgid "" -"Allows Services Operators to manipulate the SQLINE list. If\n" -"a user with a nick matching an SQLINE mask attempts to\n" -"connect, services will not allow them to pursue their IRC\n" -"session.\n" -"If the first character of the mask is #, services will\n" -"prevent the use of matching channels. If the mask is a\n" -"regular expression, the expression will be matched against\n" -"channels too." -msgstr "" -"Allows Services Operators to manipulate the SQLINE list. If\n" -"a user with a nick matching an SQLINE mask attempts to\n" -"connect, services will not allow them to pursue their IRC\n" -"session.\n" -"If the first character of the mask is #, services will\n" -"prevent the use of matching channels. If the mask is a\n" -"regular expression, the expression will be matched against\n" -"channels too." - -msgid "" -"Allows Services Operators to manipulate the list of hosts that\n" -"have specific session limits - allowing certain machines,\n" -"such as shell servers, to carry more than the default number\n" -"of clients at a time. Once a host reaches its session limit,\n" -"all clients attempting to connect from that host will be\n" -"killed. Before the user is killed, they are notified, of a\n" -"source of help regarding session limiting. The content of\n" -"this notice is a config setting." -msgstr "" -"Allows Services Operators to manipulate the list of hosts that\n" -"have specific session limits - allowing certain machines,\n" -"such as shell servers, to carry more than the default number\n" -"of clients at a time. Once a host reaches its session limit,\n" -"all clients attempting to connect from that host will be\n" -"killed. Before the user is killed, they are notified, of a\n" -"source of help regarding session limiting. The content of\n" -"this notice is a config setting." - -msgid "" -"Allows Services Operators to view the session list.\n" -" \n" -"SESSION LIST lists hosts with at least threshold sessions.\n" -"The threshold must be a number greater than 1. This is to\n" -"prevent accidental listing of the large number of single\n" -"session hosts.\n" -" \n" -"SESSION VIEW displays detailed information about a specific\n" -"host - including the current session count and session limit.\n" -"The host value may not include wildcards.\n" -" \n" -"See the EXCEPTION help for more information about session\n" -"limiting and how to set session limits specific to certain\n" -"hosts and groups thereof." -msgstr "" -"Allows Services Operators to view the session list.\n" -" \n" -"SESSION LIST lists hosts with at least threshold sessions.\n" -"The threshold must be a number greater than 1. This is to\n" -"prevent accidental listing of the large number of single\n" -"session hosts.\n" -" \n" -"SESSION VIEW displays detailed information about a specific\n" -"host - including the current session count and session limit.\n" -"The host value may not include wildcards.\n" -" \n" -"See the EXCEPTION help for more information about session\n" -"limiting and how to set session limits specific to certain\n" -"hosts and groups thereof." - -msgid "" -"Allows manipulating the topic of the specified channel.\n" -"The SET command changes the topic of the channel to the given topic\n" -"or unsets the topic if no topic is given. The APPEND command appends\n" -"the given topic to the existing topic.\n" -" \n" -"LOCK and UNLOCK may be used to enable and disable topic lock. When\n" -"topic lock is set, the channel topic will be unchangeable by users who do not have\n" -"the TOPIC privilege." -msgstr "" -"Allows manipulating the topic of the specified channel.\n" -"The SET command changes the topic of the channel to the given topic\n" -"or unsets the topic if no topic is given. The APPEND command appends\n" -"the given topic to the existing topic.\n" -" \n" -"LOCK and UNLOCK may be used to enable and disable topic lock. When\n" -"topic lock is set, the channel topic will be unchangeable by users who do not have\n" -"the TOPIC privilege." +"Allows sending messages to all users on the network. The message will be sent\n" +"from %s.\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" #, c-format msgid "" @@ -3160,6 +3203,9 @@ msgstr "Current number of SQLINEs: %zu" msgid "Current users: %zu (%d ops)" msgstr "Current users: %zu (%d ops)" +msgid "DEL entry-num" +msgstr "DEL entry-num" + msgid "DEL oper" msgstr "DEL oper" @@ -3333,6 +3379,10 @@ msgstr "Deleted %d entries from the %s list." msgid "Deleted %d entries from the AKILL list." msgstr "Deleted %d entries from the AKILL list." +#, c-format +msgid "Deleted %u entries from your message queue." +msgstr "Deleted %u entries from your message queue." + #, c-format msgid "Deleted 1 entry from %s access list." msgstr "Deleted 1 entry from %s access list." @@ -3363,6 +3413,9 @@ msgstr "Deleted info from %s." msgid "Deleted one entry from %s %s list." msgstr "Deleted one entry from %s %s list." +msgid "Deleted one entry from your message queue." +msgstr "Deleted one entry from your message queue." + msgid "" "Deletes the specified memo or memos. You can supply\n" "multiple memo numbers or ranges of numbers instead of a\n" @@ -4782,6 +4835,9 @@ msgstr "Manage the memo ignore list" msgid "Manage your auto join list" msgstr "Manage your auto join list" +msgid "Manages your pending message queue." +msgstr "Manages your pending message queue." + #, c-format msgid "Manipulate the %s list" msgstr "Manipulate the %s list" @@ -5112,6 +5168,9 @@ msgstr "No logon news items to delete!" msgid "No matches for %s found." msgstr "No matches for %s found." +msgid "No matching entries in your message queue." +msgstr "No matching entries in your message queue." + #, c-format msgid "No matching entries on %s %s list." msgstr "No matching entries on %s %s list." @@ -5840,6 +5899,9 @@ msgstr "Send a memo to all registered users" msgid "Send a message to all users" msgstr "Send a message to all users" +msgid "Send a message to all users on a server" +msgstr "Send a message to all users on a server" + msgid "Sender" msgstr "Sender" @@ -5913,6 +5975,10 @@ msgstr "" msgid "Server" msgstr "Server" +#, c-format +msgid "Server %s is not linked to the network." +msgstr "Server %s is not linked to the network." + #, c-format msgid "Server %s added to zone %s." msgstr "Server %s added to zone %s." @@ -6486,6 +6552,10 @@ msgstr "Signed kicks" msgid "Sorry, %s currently has too many memos and cannot receive more." msgstr "Sorry, %s currently has too many memos and cannot receive more." +#, c-format +msgid "Sorry, %s is temporarily unavailable." +msgstr "Sorry, %s is temporarily unavailable." + #, c-format msgid "Sorry, I have not seen %s." msgstr "Sorry, I have not seen %s." @@ -8255,6 +8325,9 @@ msgstr "You can not jupe an already juped server." msgid "You can not jupe your services' pseudoserver or your uplink server." msgstr "You can not jupe your services' pseudoserver or your uplink server." +msgid "You can not queue any more messages." +msgstr "You can not queue any more messages." + #, c-format msgid "You can not reload this module directly, instead reload %s." msgstr "You can not reload this module directly, instead reload %s." @@ -8262,6 +8335,9 @@ msgstr "You can not reload this module directly, instead reload %s." msgid "You can not request a receipt when sending a memo to yourself." msgstr "You can not request a receipt when sending a memo to yourself." +msgid "You can not send a single message while you have messages queued." +msgstr "You can not send a single message while you have messages queued." + #, c-format msgid "You can't %s yourself!" msgstr "You can't %s yourself!" @@ -8324,6 +8400,12 @@ msgstr "You currently have no memos." msgid "You do not have access to set mode %c." msgstr "You do not have access to set mode %c." +msgid "You do not have any messages queued and did not specify a message to send." +msgstr "You do not have any messages queued and did not specify a message to send." + +msgid "You do not have any queued messages." +msgstr "You do not have any queued messages." + #, c-format msgid "You do not have the access to change %s's modes." msgstr "You do not have the access to change %s's modes." @@ -8376,6 +8458,9 @@ msgstr "You have no limit on the number of memos you may keep." msgid "You have no memos." msgstr "You have no memos." +msgid "You have no messages queued." +msgstr "You have no messages queued." + msgid "You have no new memos." msgstr "You have no new memos." @@ -8554,6 +8639,12 @@ msgstr "Your memo limit is 0; you will not receive any new memos." msgid "Your memo limit is 0; you will not receive any new memos. You cannot change this limit." msgstr "Your memo limit is 0; you will not receive any new memos. You cannot change this limit." +msgid "Your message has been queued." +msgstr "Your message has been queued." + +msgid "Your message queue has been cleared." +msgstr "Your message queue has been cleared." + msgid "Your nick has been logged out." msgstr "Your nick has been logged out." @@ -8658,6 +8749,9 @@ msgstr "[channel] {num | list | LAST | NEW | ALL}" msgid "[key|#X-Y]" msgstr "[key|#X-Y]" +msgid "[message]" +msgstr "[message]" + msgid "[nick | channel]" msgstr "[nick | channel]" 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 GService; +private: + ServiceReference global; + + BotInfo *GetSender(CommandSource &source) + { + Reference 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 ¶ms) override { - const Anope::string &msg = params[0]; + if (!global) + { + source.Reply(SERVICE_UNAVAILABLE, source.service->nick.c_str()); + return; + } - if (!GService) - source.Reply("No global reference, is global loaded?"); + auto queuesize = global->CountQueue(source.nc); + if (!queuesize && params.empty()) + { + source.Reply(GLOBAL_NO_MESSAGE); + return; + } + + 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 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 &global; + CommandSource &source; + +public: + QueueDelCallback(CommandSource &src, ServiceReference& 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 global; + + void DoAdd(CommandSource &source, const Anope::string &message) + { + if (message.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + auto maxqueue = Config->GetModule(this->module)->Get("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 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 ¶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 global; + + BotInfo *GetSender(CommandSource &source) + { + Reference 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 ¶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 Global; +private: + Reference global; + PrimitiveExtensibleItem> 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 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 GetDefaultSender() const override { - if (Me->GetLinks().empty()) - return; - if (!sender) - sender = Global; - if (!sender) - return; + return global; + } - Anope::string rmessage; + const std::vector *GetQueue(NickCore* nc) const override + { + return queue.Get(nc); + } - if (!source.empty() && !Config->GetModule("global")->Get("anonymousglobal")) - rmessage = "[" + source + "] " + message; - else - rmessage = message; - - 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("client"); - + const auto glnick = conf->GetModule(this)->Get("client"); if (glnick.empty()) throw ConfigException(Module::name + ": 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("globaloncycledown"); - if (!gl.empty()) - this->SendGlobal(Global, "", gl); + const auto msg = Config->GetModule(this)->Get("globaloncycledown"); + if (!msg.empty()) + this->SendSingle(msg, nullptr, nullptr, nullptr); } void OnShutdown() override { - const Anope::string &gl = Config->GetModule(this)->Get("globaloncycledown"); - if (!gl.empty()) - this->SendGlobal(Global, "", gl); + const auto msg = Config->GetModule(this)->Get("globaloncycledown"); + if (!msg.empty()) + this->SendSingle(msg, nullptr, nullptr, nullptr); } void OnNewServer(Server *s) override { - const Anope::string &gl = Config->GetModule(this)->Get("globaloncycleup"); - if (!gl.empty() && !Me->IsSynced()) - s->Notice(Global, gl); + const auto msg = Config->GetModule(this)->Get("globaloncycleup"); + if (!msg.empty() && !Me->IsSynced()) + s->Notice(global, msg); } EventReturn OnPreHelp(CommandSource &source, const std::vector ¶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("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); } }