anope

- supernets anope source code & configuration
git clone git://git.acid.vegas/anope.git
Log | Files | Refs | Archive | README

os_news.cpp (12276B)

      1 /* OperServ core functions
      2  *
      3  * (C) 2003-2022 Anope Team
      4  * Contact us at team@anope.org
      5  *
      6  * Please read COPYING and README for further details.
      7  *
      8  * Based on the original code of Epona by Lara.
      9  * Based on the original code of Services by Andy Church.
     10  */
     11 
     12 #include "module.h"
     13 #include "modules/os_news.h"
     14 
     15 /* List of messages for each news type.  This simplifies message sending. */
     16 
     17 enum
     18 {
     19 	MSG_SYNTAX,
     20 	MSG_LIST_HEADER,
     21 	MSG_LIST_NONE,
     22 	MSG_ADDED,
     23 	MSG_DEL_NOT_FOUND,
     24 	MSG_DELETED,
     25 	MSG_DEL_NONE,
     26 	MSG_DELETED_ALL
     27 };
     28 
     29 struct NewsMessages msgarray[] = {
     30 	{NEWS_LOGON, "LOGON",
     31 	 {_("LOGONNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
     32 	  _("Logon news items:"),
     33 	  _("There is no logon news."),
     34 	  _("Added new logon news item."),
     35 	  _("Logon news item #%s not found!"),
     36 	  _("Logon news item #%d deleted."),
     37 	  _("No logon news items to delete!"),
     38 	  _("All logon news items deleted.")}
     39 	 },
     40 	{NEWS_OPER, "OPER",
     41 	 {_("OPERNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
     42 	  _("Oper news items:"),
     43 	  _("There is no oper news."),
     44 	  _("Added new oper news item."),
     45 	  _("Oper news item #%s not found!"),
     46 	  _("Oper news item #%d deleted."),
     47 	  _("No oper news items to delete!"),
     48 	  _("All oper news items deleted.")}
     49 	 },
     50 	{NEWS_RANDOM, "RANDOM",
     51 	 {_("RANDOMNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
     52 	  _("Random news items:"),
     53 	  _("There is no random news."),
     54 	  _("Added new random news item."),
     55 	  _("Random news item #%s not found!"),
     56 	  _("Random news item #%d deleted."),
     57 	  _("No random news items to delete!"),
     58 	  _("All random news items deleted.")}
     59 	 }
     60 };
     61 
     62 struct MyNewsItem : NewsItem
     63 {
     64 	void Serialize(Serialize::Data &data) const anope_override
     65 	{
     66 		data["type"] << this->type;
     67 		data["text"] << this->text;
     68 		data["who"] << this->who;
     69 		data["time"] << this->time;
     70 	}
     71 
     72 	static Serializable* Unserialize(Serializable *obj, Serialize::Data &data)
     73 	{
     74 		if (!news_service)
     75 			return NULL;
     76 
     77 		NewsItem *ni;
     78 		if (obj)
     79 			ni = anope_dynamic_static_cast<NewsItem *>(obj);
     80 		else
     81 			ni = new MyNewsItem();
     82 
     83 		unsigned int t;
     84 		data["type"] >> t;
     85 		ni->type = static_cast<NewsType>(t);
     86 		data["text"] >> ni->text;
     87 		data["who"] >> ni->who;
     88 		data["time"] >> ni->time;
     89 
     90 		if (!obj)
     91 			news_service->AddNewsItem(ni);
     92 		return ni;
     93 	}
     94 };
     95 
     96 class MyNewsService : public NewsService
     97 {
     98 	std::vector<NewsItem *> newsItems[3];
     99  public:
    100 	MyNewsService(Module *m) : NewsService(m) { }
    101 
    102 	~MyNewsService()
    103 	{
    104 		for (unsigned i = 0; i < 3; ++i)
    105 			for (unsigned j = 0; j < newsItems[i].size(); ++j)
    106 				delete newsItems[i][j];
    107 	}
    108 
    109 	NewsItem *CreateNewsItem() anope_override
    110 	{
    111 		return new MyNewsItem();
    112 	}
    113 
    114 	void AddNewsItem(NewsItem *n) anope_override
    115 	{
    116 		this->newsItems[n->type].push_back(n);
    117 	}
    118 
    119 	void DelNewsItem(NewsItem *n) anope_override
    120 	{
    121 		std::vector<NewsItem *> &list = this->GetNewsList(n->type);
    122 		std::vector<NewsItem *>::iterator it = std::find(list.begin(), list.end(), n);
    123 		if (it != list.end())
    124 			list.erase(it);
    125 		delete n;
    126 	}
    127 
    128 	std::vector<NewsItem *> &GetNewsList(NewsType t) anope_override
    129 	{
    130 		return this->newsItems[t];
    131 	}
    132 };
    133 
    134 #define lenof(a)        (sizeof(a) / sizeof(*(a)))
    135 static const char **findmsgs(NewsType type)
    136 {
    137 	for (unsigned i = 0; i < lenof(msgarray); ++i)
    138 		if (msgarray[i].type == type)
    139 			return msgarray[i].msgs;
    140 	return NULL;
    141 }
    142 
    143 class NewsBase : public Command
    144 {
    145 	ServiceReference<NewsService> ns;
    146 
    147  protected:
    148 	void DoList(CommandSource &source, NewsType ntype, const char **msgs)
    149 	{
    150 		std::vector<NewsItem *> &list = this->ns->GetNewsList(ntype);
    151 		if (list.empty())
    152 			source.Reply(msgs[MSG_LIST_NONE]);
    153 		else
    154 		{
    155 			ListFormatter lflist(source.GetAccount());
    156 			lflist.AddColumn(_("Number")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Text"));
    157 
    158 			for (unsigned i = 0, end = list.size(); i < end; ++i)
    159 			{
    160 				ListFormatter::ListEntry entry;
    161 				entry["Number"] = stringify(i + 1);
    162 				entry["Creator"] = list[i]->who;
    163 				entry["Created"] = Anope::strftime(list[i]->time, NULL, true);
    164 				entry["Text"] = list[i]->text;
    165 				lflist.AddEntry(entry);
    166 			}
    167 
    168 			source.Reply(msgs[MSG_LIST_HEADER]);
    169 
    170 			std::vector<Anope::string> replies;
    171 			lflist.Process(replies);
    172 
    173 			for (unsigned i = 0; i < replies.size(); ++i)
    174 				source.Reply(replies[i]);
    175 
    176 			source.Reply(_("End of news list."));
    177 		}
    178 
    179 		return;
    180 	}
    181 
    182 	void DoAdd(CommandSource &source, const std::vector<Anope::string> &params, NewsType ntype, const char **msgs)
    183 	{
    184 		const Anope::string text = params.size() > 1 ? params[1] : "";
    185 
    186 		if (text.empty())
    187 			this->OnSyntaxError(source, "ADD");
    188 		else
    189 		{
    190 			if (Anope::ReadOnly)
    191 				source.Reply(READ_ONLY_MODE);
    192 
    193 			NewsItem *news = new MyNewsItem();
    194 			news->type = ntype;
    195 			news->text = text;
    196 			news->time = Anope::CurTime;
    197 			news->who = source.GetNick();
    198 
    199 			this->ns->AddNewsItem(news);
    200 
    201 			source.Reply(msgs[MSG_ADDED]);
    202 			Log(LOG_ADMIN, source, this) << "to add a news item";
    203 		}
    204 
    205 		return;
    206 	}
    207 
    208 	void DoDel(CommandSource &source, const std::vector<Anope::string> &params, NewsType ntype, const char **msgs)
    209 	{
    210 		const Anope::string &text = params.size() > 1 ? params[1] : "";
    211 
    212 		if (text.empty())
    213 			this->OnSyntaxError(source, "DEL");
    214 		else
    215 		{
    216 			std::vector<NewsItem *> &list = this->ns->GetNewsList(ntype);
    217 			if (list.empty())
    218 				source.Reply(msgs[MSG_LIST_NONE]);
    219 			else
    220 			{
    221 				if (Anope::ReadOnly)
    222 					source.Reply(READ_ONLY_MODE);
    223 				if (!text.equals_ci("ALL"))
    224 				{
    225 					try
    226 					{
    227 						unsigned num = convertTo<unsigned>(text);
    228 						if (num > 0 && num <= list.size())
    229 						{
    230 							this->ns->DelNewsItem(list[num - 1]);
    231 							source.Reply(msgs[MSG_DELETED], num);
    232 							Log(LOG_ADMIN, source, this) << "to delete a news item";
    233 							return;
    234 						}
    235 					}
    236 					catch (const ConvertException &) { }
    237 
    238 					source.Reply(msgs[MSG_DEL_NOT_FOUND], text.c_str());
    239 				}
    240 				else
    241 				{
    242 					for (unsigned i = list.size(); i > 0; --i)
    243 						this->ns->DelNewsItem(list[i - 1]);
    244 					source.Reply(msgs[MSG_DELETED_ALL]);
    245 					Log(LOG_ADMIN, source, this) << "to delete all news items";
    246 				}
    247 			}
    248 		}
    249 
    250 		return;
    251 	}
    252 
    253 	void DoNews(CommandSource &source, const std::vector<Anope::string> &params, NewsType ntype)
    254 	{
    255 		if (!this->ns)
    256 			return;
    257 
    258 		const Anope::string &cmd = params[0];
    259 
    260 		const char **msgs = findmsgs(ntype);
    261 		if (!msgs)
    262 			throw CoreException("news: Invalid type to DoNews()");
    263 
    264 		if (cmd.equals_ci("LIST"))
    265 			return this->DoList(source, ntype, msgs);
    266 		else if (cmd.equals_ci("ADD"))
    267 			return this->DoAdd(source, params, ntype, msgs);
    268 		else if (cmd.equals_ci("DEL"))
    269 			return this->DoDel(source, params, ntype, msgs);
    270 		else
    271 			this->OnSyntaxError(source, "");
    272 
    273 		return;
    274 	}
    275  public:
    276 	NewsBase(Module *creator, const Anope::string &newstype) : Command(creator, newstype, 1, 2), ns("NewsService", "news")
    277 	{
    278 		this->SetSyntax(_("ADD \037text\037"));
    279 		this->SetSyntax(_("DEL {\037num\037 | ALL}"));
    280 		this->SetSyntax("LIST");
    281 	}
    282 
    283 	virtual ~NewsBase()
    284 	{
    285 	}
    286 
    287 	virtual void Execute(CommandSource &source, const std::vector<Anope::string> &params) = 0;
    288 
    289 	virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) = 0;
    290 };
    291 
    292 class CommandOSLogonNews : public NewsBase
    293 {
    294  public:
    295 	CommandOSLogonNews(Module *creator) : NewsBase(creator, "operserv/logonnews")
    296 	{
    297 		this->SetDesc(_("Define messages to be shown to users at logon"));
    298 	}
    299 
    300 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    301 	{
    302 		return this->DoNews(source, params, NEWS_LOGON);
    303 	}
    304 
    305 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    306 	{
    307 		this->SendSyntax(source);
    308 		source.Reply(" ");
    309 		source.Reply(_("Edits or displays the list of logon news messages.  When a\n"
    310 				"user connects to the network, these messages will be sent\n"
    311 				"to them.  However, no more than \002%d\002 messages will be\n"
    312 				"sent in order to avoid flooding the user.  If there are\n"
    313 				"more news messages, only the most recent will be sent."),
    314 				Config->GetModule(this->owner)->Get<unsigned>("newscount", "3"));
    315 		return true;
    316 	}
    317 };
    318 
    319 class CommandOSOperNews : public NewsBase
    320 {
    321  public:
    322 	CommandOSOperNews(Module *creator) : NewsBase(creator, "operserv/opernews")
    323 	{
    324 		this->SetDesc(_("Define messages to be shown to users who oper"));
    325 	}
    326 
    327 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    328 	{
    329 		return this->DoNews(source, params, NEWS_OPER);
    330 	}
    331 
    332 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    333 	{
    334 		this->SendSyntax(source);
    335 		source.Reply(" ");
    336 		source.Reply(_("Edits or displays the list of oper news messages.  When a\n"
    337 				"user opers up (with the /OPER command), these messages will\n"
    338 				"be sent to them.  However, no more than \002%d\002 messages will\n"
    339 				"be sent in order to avoid flooding the user.  If there are\n"
    340 				"more news messages, only the most recent will be sent."),
    341 				Config->GetModule(this->owner)->Get<unsigned>("newscount", "3"));
    342 		return true;
    343 	}
    344 };
    345 
    346 class CommandOSRandomNews : public NewsBase
    347 {
    348  public:
    349 	CommandOSRandomNews(Module *creator) : NewsBase(creator, "operserv/randomnews")
    350 	{
    351 		this->SetDesc(_("Define messages to be randomly shown to users at logon"));
    352 	}
    353 
    354 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    355 	{
    356 		return this->DoNews(source, params, NEWS_RANDOM);
    357 	}
    358 
    359 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    360 	{
    361 		this->SendSyntax(source);
    362 		source.Reply(" ");
    363 		source.Reply(_("Edits or displays the list of random news messages.  When a\n"
    364 				"user connects to the network, one (and only one) of the\n"
    365 				"random news will be randomly chosen and sent to them."));
    366 		return true;
    367 	}
    368 };
    369 
    370 static unsigned cur_rand_news = 0;
    371 
    372 class OSNews : public Module
    373 {
    374 	MyNewsService newsservice;
    375 	Serialize::Type newsitem_type;
    376 
    377 	CommandOSLogonNews commandoslogonnews;
    378 	CommandOSOperNews commandosopernews;
    379 	CommandOSRandomNews commandosrandomnews;
    380 
    381 	Anope::string oper_announcer, announcer;
    382 	unsigned news_count;
    383 
    384 	void DisplayNews(User *u, NewsType Type)
    385 	{
    386 		std::vector<NewsItem *> &newsList = this->newsservice.GetNewsList(Type);
    387 		if (newsList.empty())
    388 			return;
    389 
    390 		BotInfo *bi = NULL;
    391 		if (Type == NEWS_OPER)
    392 			bi = BotInfo::Find(Config->GetModule(this)->Get<const Anope::string>("oper_announcer", "OperServ"), true);
    393 		else
    394 			bi = BotInfo::Find(Config->GetModule(this)->Get<const Anope::string>("announcer", "Global"), true);
    395 		if (bi == NULL)
    396 			return;
    397 
    398 		Anope::string msg;
    399 		if (Type == NEWS_LOGON)
    400 			msg = _("[\002Logon News\002 - %s] %s");
    401 		else if (Type == NEWS_OPER)
    402 			msg = _("[\002Oper News\002 - %s] %s");
    403 		else if (Type == NEWS_RANDOM)
    404 			msg = _("[\002Random News\002 - %s] %s");
    405 
    406 		int start = 0;
    407 
    408 		if (Type != NEWS_RANDOM)
    409 		{
    410 			start = newsList.size() - news_count;
    411 			if (start < 0)
    412 				start = 0;
    413 		}
    414 
    415 		for (unsigned i = start, end = newsList.size(); i < end; ++i)
    416 		{
    417 			if (Type == NEWS_RANDOM && i != cur_rand_news)
    418 				continue;
    419 
    420 			u->SendMessage(bi, msg.c_str(), Anope::strftime(newsList[i]->time, u->Account(), true).c_str(), newsList[i]->text.c_str());
    421 
    422 			if (Type == NEWS_RANDOM)
    423 			{
    424 				++cur_rand_news;
    425 				break;
    426 			}
    427 		}
    428 
    429 		/* Reset to head of list to get first random news value */
    430 		if (Type == NEWS_RANDOM && cur_rand_news >= newsList.size())
    431 			cur_rand_news = 0;
    432 	}
    433 
    434  public:
    435 	OSNews(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    436 		newsservice(this), newsitem_type("NewsItem", MyNewsItem::Unserialize),
    437 		commandoslogonnews(this), commandosopernews(this), commandosrandomnews(this)
    438 	{
    439 	}
    440 
    441 	void OnReload(Configuration::Conf *conf) anope_override
    442 	{
    443 		oper_announcer = conf->GetModule(this)->Get<const Anope::string>("oper_announcer", "OperServ");
    444 		announcer = conf->GetModule(this)->Get<const Anope::string>("announcer", "Global");
    445 		news_count = conf->GetModule(this)->Get<unsigned>("newscount", "3");
    446 	}
    447 
    448 	void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) anope_override
    449 	{
    450 		if (mname == "OPER")
    451 			DisplayNews(u, NEWS_OPER);
    452 	}
    453 
    454 	void OnUserConnect(User *user, bool &) anope_override
    455 	{
    456 		if (user->Quitting() || !user->server->IsSynced())
    457 			return;
    458 
    459 		DisplayNews(user, NEWS_LOGON);
    460 		DisplayNews(user, NEWS_RANDOM);
    461 	}
    462 };
    463 
    464 MODULE_INIT(OSNews)