diff options
Diffstat (limited to 'modules/operserv/os_news.cpp')
-rw-r--r-- | modules/operserv/os_news.cpp | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/modules/operserv/os_news.cpp b/modules/operserv/os_news.cpp new file mode 100644 index 000000000..a862e177f --- /dev/null +++ b/modules/operserv/os_news.cpp @@ -0,0 +1,474 @@ +/* OperServ 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" +#include "modules/os_news.h" + +// TODO: msgarray breaks the format string checking +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wformat-security" +#endif + +/* List of messages for each news type. This simplifies message sending. */ + +enum +{ + MSG_SYNTAX, + MSG_LIST_HEADER, + MSG_LIST_NONE, + MSG_ADDED, + MSG_DEL_NOT_FOUND, + MSG_DELETED, + MSG_DEL_NONE, + MSG_DELETED_ALL +}; + +struct NewsMessages msgarray[] = { + {NEWS_LOGON, "LOGON", + {_("LOGONNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Logon news items:"), + _("There is no logon news."), + _("Added new logon news item."), + _("Logon news item #%s not found!"), + _("Logon news item #%d deleted."), + _("No logon news items to delete!"), + _("All logon news items deleted.")} + }, + {NEWS_OPER, "OPER", + {_("OPERNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Oper news items:"), + _("There is no oper news."), + _("Added new oper news item."), + _("Oper news item #%s not found!"), + _("Oper news item #%d deleted."), + _("No oper news items to delete!"), + _("All oper news items deleted.")} + }, + {NEWS_RANDOM, "RANDOM", + {_("RANDOMNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Random news items:"), + _("There is no random news."), + _("Added new random news item."), + _("Random news item #%s not found!"), + _("Random news item #%d deleted."), + _("No random news items to delete!"), + _("All random news items deleted.")} + } +}; + +struct MyNewsItem final + : NewsItem +{ + void Serialize(Serialize::Data &data) const override + { + data.Store("type", this->type); + data.Store("text", this->text); + data.Store("who", this->who); + data.Store("time", this->time); + } + + static Serializable *Unserialize(Serializable *obj, Serialize::Data &data) + { + if (!news_service) + return NULL; + + NewsItem *ni; + if (obj) + ni = anope_dynamic_static_cast<NewsItem *>(obj); + else + ni = new MyNewsItem(); + + unsigned int t; + data["type"] >> t; + ni->type = static_cast<NewsType>(t); + data["text"] >> ni->text; + data["who"] >> ni->who; + data["time"] >> ni->time; + + if (!obj) + news_service->AddNewsItem(ni); + return ni; + } +}; + +class MyNewsService final + : public NewsService +{ + std::vector<NewsItem *> newsItems[3]; +public: + MyNewsService(Module *m) : NewsService(m) { } + + ~MyNewsService() override + { + for (const auto &newstype : newsItems) + { + for (const auto *newsitem : newstype) + delete newsitem; + } + } + + NewsItem *CreateNewsItem() override + { + return new MyNewsItem(); + } + + void AddNewsItem(NewsItem *n) override + { + this->newsItems[n->type].push_back(n); + } + + void DelNewsItem(NewsItem *n) override + { + std::vector<NewsItem *> &list = this->GetNewsList(n->type); + std::vector<NewsItem *>::iterator it = std::find(list.begin(), list.end(), n); + if (it != list.end()) + list.erase(it); + delete n; + } + + std::vector<NewsItem *> &GetNewsList(NewsType t) override + { + return this->newsItems[t]; + } +}; + +#define lenof(a) (sizeof(a) / sizeof(*(a))) +static const char **findmsgs(NewsType type) +{ + for (auto &msg : msgarray) + if (msg.type == type) + return msg.msgs; + return NULL; +} + +class NewsBase + : public Command +{ + ServiceReference<NewsService> ns; + +protected: + void DoList(CommandSource &source, NewsType ntype, const char **msgs) + { + std::vector<NewsItem *> &list = this->ns->GetNewsList(ntype); + if (list.empty()) + source.Reply(msgs[MSG_LIST_NONE]); + else + { + ListFormatter lflist(source.GetAccount()); + lflist.AddColumn(_("Number")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Text")); + + for (unsigned i = 0, end = list.size(); i < end; ++i) + { + ListFormatter::ListEntry entry; + entry["Number"] = Anope::ToString(i + 1); + entry["Creator"] = list[i]->who; + entry["Created"] = Anope::strftime(list[i]->time, NULL, true); + entry["Text"] = list[i]->text; + lflist.AddEntry(entry); + } + + source.Reply(msgs[MSG_LIST_HEADER]); + + std::vector<Anope::string> replies; + lflist.Process(replies); + + for (const auto &reply : replies) + source.Reply(reply); + + source.Reply(_("End of news list.")); + } + + return; + } + + void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms, NewsType ntype, const char **msgs) + { + const Anope::string text = params.size() > 1 ? params[1] : ""; + + if (text.empty()) + this->OnSyntaxError(source, "ADD"); + else + { + if (Anope::ReadOnly) + source.Reply(READ_ONLY_MODE); + + NewsItem *news = new MyNewsItem(); + news->type = ntype; + news->text = text; + news->time = Anope::CurTime; + news->who = source.GetNick(); + + this->ns->AddNewsItem(news); + + source.Reply(msgs[MSG_ADDED]); + Log(LOG_ADMIN, source, this) << "to add a news item"; + } + + return; + } + + void DoDel(CommandSource &source, const std::vector<Anope::string> ¶ms, NewsType ntype, const char **msgs) + { + const Anope::string &text = params.size() > 1 ? params[1] : ""; + + if (text.empty()) + this->OnSyntaxError(source, "DEL"); + else + { + std::vector<NewsItem *> &list = this->ns->GetNewsList(ntype); + if (list.empty()) + source.Reply(msgs[MSG_LIST_NONE]); + else + { + if (Anope::ReadOnly) + source.Reply(READ_ONLY_MODE); + if (!text.equals_ci("ALL")) + { + unsigned num = Anope::Convert<unsigned>(text, 0); + if (num > 0 && num <= list.size()) + { + this->ns->DelNewsItem(list[num - 1]); + source.Reply(msgs[MSG_DELETED], num); + Log(LOG_ADMIN, source, this) << "to delete a news item"; + return; + } + + source.Reply(msgs[MSG_DEL_NOT_FOUND], text.c_str()); + } + else + { + for (unsigned i = list.size(); i > 0; --i) + this->ns->DelNewsItem(list[i - 1]); + source.Reply(msgs[MSG_DELETED_ALL]); + Log(LOG_ADMIN, source, this) << "to delete all news items"; + } + } + } + + return; + } + + void DoNews(CommandSource &source, const std::vector<Anope::string> ¶ms, NewsType ntype) + { + if (!this->ns) + return; + + const Anope::string &cmd = params[0]; + + const char **msgs = findmsgs(ntype); + if (!msgs) + throw CoreException("news: Invalid type to DoNews()"); + + if (cmd.equals_ci("LIST")) + return this->DoList(source, ntype, msgs); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params, ntype, msgs); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params, ntype, msgs); + else + this->OnSyntaxError(source, ""); + + return; + } +public: + NewsBase(Module *creator, const Anope::string &newstype) : Command(creator, newstype, 1, 2), ns("NewsService", "news") + { + this->SetSyntax(_("ADD \037text\037")); + this->SetSyntax(_("DEL {\037num\037 | ALL}")); + this->SetSyntax("LIST"); + } + + ~NewsBase() override + { + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override = 0; + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override = 0; +}; + +class CommandOSLogonNews final + : public NewsBase +{ +public: + CommandOSLogonNews(Module *creator) : NewsBase(creator, "operserv/logonnews") + { + this->SetDesc(_("Define messages to be shown to users at logon")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + return this->DoNews(source, params, NEWS_LOGON); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Edits or displays the list of logon news messages. When a\n" + "user connects to the network, these messages will be sent\n" + "to them. However, no more than \002%d\002 messages will be\n" + "sent in order to avoid flooding the user. If there are\n" + "more news messages, only the most recent will be sent."), + Config->GetModule(this->owner)->Get<unsigned>("newscount", "3")); + return true; + } +}; + +class CommandOSOperNews final + : public NewsBase +{ +public: + CommandOSOperNews(Module *creator) : NewsBase(creator, "operserv/opernews") + { + this->SetDesc(_("Define messages to be shown to users who oper")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + return this->DoNews(source, params, NEWS_OPER); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Edits or displays the list of oper news messages. When a\n" + "user opers up (with the /OPER command), these messages will\n" + "be sent to them. However, no more than \002%d\002 messages will\n" + "be sent in order to avoid flooding the user. If there are\n" + "more news messages, only the most recent will be sent."), + Config->GetModule(this->owner)->Get<unsigned>("newscount", "3")); + return true; + } +}; + +class CommandOSRandomNews final + : public NewsBase +{ +public: + CommandOSRandomNews(Module *creator) : NewsBase(creator, "operserv/randomnews") + { + this->SetDesc(_("Define messages to be randomly shown to users at logon")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + return this->DoNews(source, params, NEWS_RANDOM); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Edits or displays the list of random news messages. When a\n" + "user connects to the network, one (and only one) of the\n" + "random news will be randomly chosen and sent to them.")); + return true; + } +}; + +static unsigned cur_rand_news = 0; + +class OSNews final + : public Module +{ + MyNewsService newsservice; + Serialize::Type newsitem_type; + + CommandOSLogonNews commandoslogonnews; + CommandOSOperNews commandosopernews; + CommandOSRandomNews commandosrandomnews; + + Anope::string oper_announcer, announcer; + unsigned news_count; + + void DisplayNews(User *u, NewsType Type) + { + std::vector<NewsItem *> &newsList = this->newsservice.GetNewsList(Type); + if (newsList.empty()) + return; + + BotInfo *bi = NULL; + if (Type == NEWS_OPER) + bi = BotInfo::Find(Config->GetModule(this)->Get<const Anope::string>("oper_announcer", "OperServ"), true); + else + bi = BotInfo::Find(Config->GetModule(this)->Get<const Anope::string>("announcer", "Global"), true); + if (bi == NULL) + return; + + Anope::string msg; + if (Type == NEWS_LOGON) + msg = _("[\002Logon News\002 - %s] %s"); + else if (Type == NEWS_OPER) + msg = _("[\002Oper News\002 - %s] %s"); + else if (Type == NEWS_RANDOM) + msg = _("[\002Random News\002 - %s] %s"); + + int start = 0; + + if (Type != NEWS_RANDOM) + { + start = newsList.size() - news_count; + if (start < 0) + start = 0; + } + + for (unsigned i = start, end = newsList.size(); i < end; ++i) + { + if (Type == NEWS_RANDOM && i != cur_rand_news) + continue; + + u->SendMessage(bi, msg.c_str(), Anope::strftime(newsList[i]->time, u->Account(), true).c_str(), newsList[i]->text.c_str()); + + if (Type == NEWS_RANDOM) + { + ++cur_rand_news; + break; + } + } + + /* Reset to head of list to get first random news value */ + if (Type == NEWS_RANDOM && cur_rand_news >= newsList.size()) + cur_rand_news = 0; + } + +public: + OSNews(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + newsservice(this), newsitem_type("NewsItem", MyNewsItem::Unserialize), + commandoslogonnews(this), commandosopernews(this), commandosrandomnews(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + oper_announcer = conf->GetModule(this)->Get<const Anope::string>("oper_announcer", "OperServ"); + announcer = conf->GetModule(this)->Get<const Anope::string>("announcer", "Global"); + news_count = conf->GetModule(this)->Get<unsigned>("newscount", "3"); + } + + void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (mname == "OPER") + DisplayNews(u, NEWS_OPER); + } + + void OnUserConnect(User *user, bool &) override + { + if (user->Quitting() || !user->server->IsSynced()) + return; + + DisplayNews(user, NEWS_LOGON); + DisplayNews(user, NEWS_RANDOM); + } +}; + +MODULE_INIT(OSNews) |