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> ¶ms, 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> ¶ms, 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> ¶ms, 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> ¶ms) = 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> ¶ms) 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> ¶ms) 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> ¶ms) 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)