os_news.cpp

Go to the documentation of this file.
00001 /* OperServ core functions
00002  *
00003  * (C) 2003-2013 Anope Team
00004  * Contact us at info@anope.org
00005  *
00006  * Please read COPYING and README for further details.
00007  *
00008  * Based on the original code of Epona by Lara.
00009  * Based on the original code of Services by Andy Church.
00010  */
00011 
00012 /*************************************************************************/
00013 
00014 #include "module.h"
00015 #include "global.h"
00016 #include "os_news.h"
00017 
00018 /* List of messages for each news type.  This simplifies message sending. */
00019 
00020 enum
00021 {
00022         MSG_SYNTAX,
00023         MSG_LIST_HEADER,
00024         MSG_LIST_NONE,
00025         MSG_ADDED,
00026         MSG_DEL_NOT_FOUND,
00027         MSG_DELETED,
00028         MSG_DEL_NONE,
00029         MSG_DELETED_ALL
00030 };
00031 
00032 struct NewsMessages msgarray[] = {
00033         {NEWS_LOGON, "LOGON",
00034          {_("LOGONNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
00035           _("Logon news items:"),
00036           _("There is no logon news."),
00037           _("Added new logon news item."),
00038           _("Logon news item #%s not found!"),
00039           _("Logon news item #%d deleted."),
00040           _("No logon news items to delete!"),
00041           _("All logon news items deleted.")}
00042          },
00043         {NEWS_OPER, "OPER",
00044          {_("OPERNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
00045           _("Oper news items:"),
00046           _("There is no oper news."),
00047           _("Added new oper news item."),
00048           _("Oper news item #%s not found!"),
00049           _("Oper news item #%d deleted."),
00050           _("No oper news items to delete!"),
00051           _("All oper news items deleted.")}
00052          },
00053         {NEWS_RANDOM, "RANDOM",
00054          {_("RANDOMNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
00055           _("Random news items:"),
00056           _("There is no random news."),
00057           _("Added new random news item."),
00058           _("Random news item #%s not found!"),
00059           _("Random news item #%d deleted."),
00060           _("No random news items to delete!"),
00061           _("All random news items deleted.")}
00062          }
00063 };
00064 
00065 class MyNewsService : public NewsService
00066 {
00067         std::vector<NewsItem *> newsItems[3];
00068  public:
00069         MyNewsService(Module *m) : NewsService(m) { }
00070 
00071         ~MyNewsService()
00072         {
00073                 for (unsigned i = 0; i < 3; ++i)
00074                         for (unsigned j = 0; j < newsItems[i].size(); ++j)
00075                                 delete newsItems[i][j];
00076         }
00077 
00078         void AddNewsItem(NewsItem *n)
00079         {
00080                 this->newsItems[n->type].push_back(n);
00081         }
00082         
00083         void DelNewsItem(NewsItem *n)
00084         {
00085                 std::vector<NewsItem *> &list = this->GetNewsList(n->type);
00086                 std::vector<NewsItem *>::iterator it = std::find(list.begin(), list.end(), n);
00087                 if (it != list.end())
00088                         list.erase(it);
00089                 delete n;
00090         }
00091 
00092         std::vector<NewsItem *> &GetNewsList(NewsType t)
00093         {
00094                 return this->newsItems[t];
00095         }
00096 };
00097 
00098 #define lenof(a)        (sizeof(a) / sizeof(*(a)))
00099 static const char **findmsgs(NewsType type)
00100 {
00101         for (unsigned i = 0; i < lenof(msgarray); ++i)
00102                 if (msgarray[i].type == type)
00103                         return msgarray[i].msgs;
00104         return NULL;
00105 }
00106 
00107 class NewsBase : public Command
00108 {
00109         ServiceReference<NewsService> ns;
00110 
00111  protected:
00112         void DoList(CommandSource &source, NewsType ntype, const char **msgs)
00113         {
00114                 std::vector<NewsItem *> &list = this->ns->GetNewsList(ntype);
00115                 if (list.empty())
00116                         source.Reply(msgs[MSG_LIST_NONE]);
00117                 else
00118                 {
00119                         ListFormatter lflist;
00120                         lflist.AddColumn("Number").AddColumn("Creator").AddColumn("Created").AddColumn("Text");
00121 
00122                         for (unsigned i = 0, end = list.size(); i < end; ++i)
00123                         {
00124                                 ListFormatter::ListEntry entry;
00125                                 entry["Number"] = stringify(i + 1);
00126                                 entry["Creator"] = list[i]->who;
00127                                 entry["Created"] = Anope::strftime(list[i]->time);
00128                                 entry["Text"] = list[i]->text;
00129                                 lflist.AddEntry(entry);
00130                         }
00131 
00132                         source.Reply(msgs[MSG_LIST_HEADER]);
00133 
00134                         std::vector<Anope::string> replies;
00135                         lflist.Process(replies);
00136 
00137                         for (unsigned i = 0; i < replies.size(); ++i)
00138                                 source.Reply(replies[i]);
00139 
00140                         source.Reply(_("End of news list."));
00141                 }
00142 
00143                 return;
00144         }
00145 
00146         void DoAdd(CommandSource &source, const std::vector<Anope::string> &params, NewsType ntype, const char **msgs)
00147         {
00148                 const Anope::string text = params.size() > 1 ? params[1] : "";
00149 
00150                 if (text.empty())
00151                         this->OnSyntaxError(source, "ADD");
00152                 else
00153                 {
00154                         if (Anope::ReadOnly)
00155                                 source.Reply(READ_ONLY_MODE);
00156 
00157                         NewsItem *news = new NewsItem();
00158                         news->type = ntype;
00159                         news->text = text;
00160                         news->time = Anope::CurTime;
00161                         news->who = source.GetNick();
00162 
00163                         this->ns->AddNewsItem(news);
00164 
00165                         source.Reply(msgs[MSG_ADDED]);
00166                 }
00167 
00168                 return;
00169         }
00170 
00171         void DoDel(CommandSource &source, const std::vector<Anope::string> &params, NewsType ntype, const char **msgs)
00172         {
00173                 const Anope::string &text = params.size() > 1 ? params[1] : "";
00174 
00175                 if (text.empty())
00176                         this->OnSyntaxError(source, "DEL");
00177                 else
00178                 {
00179                         std::vector<NewsItem *> &list = this->ns->GetNewsList(ntype);
00180                         if (list.empty())
00181                                 source.Reply(msgs[MSG_LIST_NONE]);
00182                         else
00183                         {
00184                                 if (Anope::ReadOnly)
00185                                         source.Reply(READ_ONLY_MODE);
00186                                 if (!text.equals_ci("ALL"))
00187                                 {
00188                                         try
00189                                         {
00190                                                 unsigned num = convertTo<unsigned>(text);
00191                                                 if (num > 0 && num <= list.size())
00192                                                 {
00193                                                         this->ns->DelNewsItem(list[num - 1]);
00194                                                         source.Reply(msgs[MSG_DELETED], num);
00195                                                         return;
00196                                                 }
00197                                         }
00198                                         catch (const ConvertException &) { }
00199 
00200                                         source.Reply(msgs[MSG_DEL_NOT_FOUND], text.c_str());
00201                                 }
00202                                 else
00203                                 {
00204                                         for (unsigned i = list.size(); i > 0; --i)
00205                                                 this->ns->DelNewsItem(list[i - 1]);
00206                                         source.Reply(msgs[MSG_DELETED_ALL]);
00207                                 }
00208                         }
00209                 }
00210 
00211                 return;
00212         }
00213 
00214         void DoNews(CommandSource &source, const std::vector<Anope::string> &params, NewsType ntype)
00215         {
00216                 if (!this->ns)
00217                         return;
00218 
00219                 const Anope::string &cmd = params[0];
00220 
00221                 const char **msgs = findmsgs(ntype);
00222                 if (!msgs)
00223                         throw CoreException("news: Invalid type to do_news()");
00224 
00225                 if (cmd.equals_ci("LIST"))
00226                         return this->DoList(source, ntype, msgs);
00227                 else if (cmd.equals_ci("ADD"))
00228                         return this->DoAdd(source, params, ntype, msgs);
00229                 else if (cmd.equals_ci("DEL"))
00230                         return this->DoDel(source, params, ntype, msgs);
00231                 else
00232                         this->OnSyntaxError(source, "");
00233 
00234                 return;
00235         }
00236  public:
00237         NewsBase(Module *creator, const Anope::string &newstype) : Command(creator, newstype, 1, 2), ns("NewsService", "news")
00238         {
00239                 this->SetSyntax(_("ADD \037text\037"));
00240                 this->SetSyntax(_("DEL {\037num\037 | ALL}"));
00241                 this->SetSyntax(_("LIST"));
00242         }
00243 
00244         virtual ~NewsBase()
00245         {
00246         }
00247 
00248         virtual void Execute(CommandSource &source, const std::vector<Anope::string> &params) = 0;
00249 
00250         virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) = 0;
00251 };
00252 
00253 class CommandOSLogonNews : public NewsBase
00254 {
00255  public:
00256         CommandOSLogonNews(Module *creator) : NewsBase(creator, "operserv/logonnews")
00257         {
00258                 this->SetDesc(_("Define messages to be shown to users at logon"));
00259         }
00260 
00261         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00262         {
00263                 return this->DoNews(source, params, NEWS_LOGON);
00264         }
00265 
00266         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00267         {
00268                 this->SendSyntax(source);
00269                 source.Reply(" ");
00270                 source.Reply(_("Edits or displays the list of logon news messages.  When a\n"
00271                         "user connects to the network, these messages will be sent\n"
00272                         "to them.  (However, no more than \002%d\002 messages will be\n"
00273                         "sent in order to avoid flooding the user.  If there are\n"
00274                         "more news messages, only the most recent will be sent.)\n"
00275                         "NewsCount can be configured in services.conf.\n"
00276                         " \n"
00277                         "LOGONNEWS may only be used by Services Operators."), Config->NewsCount);
00278                 return true;
00279         }
00280 };
00281 
00282 class CommandOSOperNews : public NewsBase
00283 {
00284  public:
00285         CommandOSOperNews(Module *creator) : NewsBase(creator, "operserv/opernews")
00286         {
00287                 this->SetDesc(_("Define messages to be shown to users who oper"));
00288         }
00289 
00290         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00291         {
00292                 return this->DoNews(source, params, NEWS_OPER);
00293         }
00294 
00295         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00296         {
00297                 this->SendSyntax(source);
00298                 source.Reply(" ");
00299                 source.Reply(_("Edits or displays the list of oper news messages.  When a\n"
00300                                 "user opers up (with the /OPER command), these messages will\n"
00301                                 "be sent to them.  (However, no more than \002%d\002 messages will\n"
00302                                 "be sent in order to avoid flooding the user.  If there are\n"
00303                                 "more news messages, only the most recent will be sent.)\n"
00304                                 "NewsCount can be configured in services.conf.\n"
00305                                 " \n"
00306                                 "OPERNEWS may only be used by Services Operators."), Config->NewsCount);
00307                 return true;
00308         }
00309 };
00310 
00311 class CommandOSRandomNews : public NewsBase
00312 {
00313  public:
00314         CommandOSRandomNews(Module *creator) : NewsBase(creator, "operserv/randomnews")
00315         {
00316                 this->SetDesc(_("Define messages to be randomly shown to users at logon"));
00317         }
00318 
00319         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00320         {
00321                 return this->DoNews(source, params, NEWS_RANDOM);
00322         }
00323 
00324         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00325         {
00326                 this->SendSyntax(source);
00327                 source.Reply(" ");
00328                 source.Reply(_("Edits or displays the list of random news messages.  When a\n"
00329                                 "user connects to the network, one (and only one) of the\n"
00330                                 "random news will be randomly chosen and sent to them.\n"
00331                                 " \n"
00332                                 "RANDOMNEWS may only be used by Services Operators."));
00333                 return true;
00334         }
00335 };
00336 
00337 class OSNews : public Module
00338 {
00339         Serialize::Type newsitem_type;
00340         MyNewsService newsservice;
00341 
00342         CommandOSLogonNews commandoslogonnews;
00343         CommandOSOperNews commandosopernews;
00344         CommandOSRandomNews commandosrandomnews;
00345 
00346         void DisplayNews(User *u, NewsType Type)
00347         {
00348                 std::vector<NewsItem *> &newsList = this->newsservice.GetNewsList(Type);
00349                 if (newsList.empty())
00350                         return;
00351 
00352                 Anope::string msg;
00353                 if (Type == NEWS_LOGON)
00354                         msg = _("[\002Logon News\002 - %s] %s");
00355                 else if (Type == NEWS_OPER)
00356                         msg = _("[\002Oper News\002 - %s] %s");
00357                 else if (Type == NEWS_RANDOM)
00358                         msg = _("[\002Random News\002 - %s] %s");
00359 
00360                 static unsigned cur_rand_news = 0;
00361                 unsigned displayed = 0;
00362                 for (unsigned i = 0, end = newsList.size(); i < end; ++i)
00363                 {
00364                         if (Type == NEWS_RANDOM && i != cur_rand_news)
00365                                 continue;
00366 
00367                         const BotInfo *gl = Global;
00368                         if (!gl && !BotListByNick->empty())
00369                                 gl = BotListByNick->begin()->second;
00370                         const BotInfo *os = OperServ;
00371                         if (!os)
00372                                 os = gl;
00373                         if (gl)
00374                                 u->SendMessage(Type != NEWS_OPER ? gl : os, msg.c_str(), Anope::strftime(newsList[i]->time).c_str(), newsList[i]->text.c_str());
00375 
00376                         ++displayed;
00377 
00378                         if (Type == NEWS_RANDOM)
00379                         {
00380                                 ++cur_rand_news;
00381                                 break;
00382                         }
00383                         else if (displayed >= Config->NewsCount)
00384                                 break;
00385                 }
00386 
00387                 /* Reset to head of list to get first random news value */
00388                 if (Type == NEWS_RANDOM && cur_rand_news >= newsList.size())
00389                         cur_rand_news = 0;
00390         }
00391 
00392  public:
00393         OSNews(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE),
00394                 newsitem_type("NewsItem", NewsItem::Unserialize), newsservice(this), commandoslogonnews(this), commandosopernews(this), commandosrandomnews(this)
00395         {
00396                 this->SetAuthor("Anope");
00397 
00398                 Implementation i[] = { I_OnUserModeSet, I_OnUserConnect };
00399                 ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
00400         }
00401 
00402         void OnUserModeSet(User *u, const Anope::string &mname) anope_override
00403         {
00404                 if (mname == "OPER")
00405                         DisplayNews(u, NEWS_OPER);
00406         }
00407 
00408         void OnUserConnect(User *user, bool &) anope_override
00409         {
00410                 if (user->Quitting() || !user->server->IsSynced())
00411                         return;
00412 
00413                 DisplayNews(user, NEWS_LOGON);
00414                 DisplayNews(user, NEWS_RANDOM);
00415         }
00416 };
00417 
00418 MODULE_INIT(OSNews)