diff options
| author | Sadie Powell <sadie@witchery.services> | 2024-01-24 12:34:03 +0000 |
|---|---|---|
| committer | Sadie Powell <sadie@witchery.services> | 2024-01-24 12:34:34 +0000 |
| commit | 484160eb4ed560eeda97c94a42dd8e31431ab251 (patch) | |
| tree | 03600af16b55fa2f268904d7a42a8b87f86c09f4 /modules/memoserv | |
| parent | 7ac1fe58478d58e2480b6919c4abf3a82929169c (diff) | |
Shuffle modules around a bit.
Diffstat (limited to 'modules/memoserv')
| -rw-r--r-- | modules/memoserv/memoserv.cpp | 237 | ||||
| -rw-r--r-- | modules/memoserv/ms_cancel.cpp | 103 | ||||
| -rw-r--r-- | modules/memoserv/ms_check.cpp | 89 | ||||
| -rw-r--r-- | modules/memoserv/ms_del.cpp | 162 | ||||
| -rw-r--r-- | modules/memoserv/ms_ignore.cpp | 134 | ||||
| -rw-r--r-- | modules/memoserv/ms_info.cpp | 235 | ||||
| -rw-r--r-- | modules/memoserv/ms_list.cpp | 167 | ||||
| -rw-r--r-- | modules/memoserv/ms_read.cpp | 227 | ||||
| -rw-r--r-- | modules/memoserv/ms_rsend.cpp | 106 | ||||
| -rw-r--r-- | modules/memoserv/ms_send.cpp | 90 | ||||
| -rw-r--r-- | modules/memoserv/ms_sendall.cpp | 70 | ||||
| -rw-r--r-- | modules/memoserv/ms_set.cpp | 317 | ||||
| -rw-r--r-- | modules/memoserv/ms_staff.cpp | 67 |
13 files changed, 2004 insertions, 0 deletions
diff --git a/modules/memoserv/memoserv.cpp b/modules/memoserv/memoserv.cpp new file mode 100644 index 000000000..0961017e0 --- /dev/null +++ b/modules/memoserv/memoserv.cpp @@ -0,0 +1,237 @@ +/* MemoServ 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 MemoServCore final + : public Module + , public MemoServService +{ + Reference<BotInfo> MemoServ; + + bool SendMemoMail(NickCore *nc, MemoInfo *mi, Memo *m) + { + Anope::string subject = Language::Translate(nc, Config->GetBlock("mail")->Get<const Anope::string>("memo_subject").c_str()), + message = Language::Translate(Config->GetBlock("mail")->Get<const Anope::string>("memo_message").c_str()); + + subject = subject.replace_all_cs("%n", nc->display); + subject = subject.replace_all_cs("%s", m->sender); + subject = subject.replace_all_cs("%d", stringify(mi->GetIndex(m) + 1)); + subject = subject.replace_all_cs("%t", m->text); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get<const Anope::string>("networkname")); + + message = message.replace_all_cs("%n", nc->display); + message = message.replace_all_cs("%s", m->sender); + message = message.replace_all_cs("%d", stringify(mi->GetIndex(m) + 1)); + message = message.replace_all_cs("%t", m->text); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get<const Anope::string>("networkname")); + + return Mail::Send(nc, subject, message); + } + +public: + MemoServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR), + MemoServService(this) + { + } + + MemoResult Send(const Anope::string &source, const Anope::string &target, const Anope::string &message, bool force) override + { + bool ischan; + MemoInfo *mi = MemoInfo::GetMemoInfo(target, ischan); + + if (mi == NULL) + return MEMO_INVALID_TARGET; + + Anope::string sender_display = source; + + User *sender = User::Find(source, true); + if (sender != NULL) + { + if (!sender->HasPriv("memoserv/no-limit") && !force) + { + time_t send_delay = Config->GetModule("memoserv")->Get<time_t>("senddelay"); + if (send_delay > 0 && sender->lastmemosend + send_delay > Anope::CurTime) + return MEMO_TOO_FAST; + else if (!mi->memomax) + return MEMO_TARGET_FULL; + else if (mi->memomax > 0 && mi->memos->size() >= static_cast<unsigned>(mi->memomax)) + return MEMO_TARGET_FULL; + else if (mi->HasIgnore(sender)) + return MEMO_SUCCESS; + } + + NickCore *acc = sender->Account(); + if (acc != NULL) + { + sender_display = acc->display; + } + } + + if (sender != NULL) + sender->lastmemosend = Anope::CurTime; + + auto *m = new Memo(); + m->mi = mi; + mi->memos->push_back(m); + m->owner = target; + m->sender = sender_display; + m->time = Anope::CurTime; + m->text = message; + m->unread = true; + + FOREACH_MOD(OnMemoSend, (source, target, mi, m)); + + if (ischan) + { + ChannelInfo *ci = ChannelInfo::Find(target); + + if (ci->c) + { + for (const auto &[_, cu] : ci->c->users) + { + if (ci->AccessFor(cu->user).HasPriv("MEMO")) + { + if (cu->user->Account() && cu->user->Account()->HasExt("MEMO_RECEIVE")) + cu->user->SendMessage(MemoServ, MEMO_NEW_X_MEMO_ARRIVED, ci->name.c_str(), Config->StrictPrivmsg.c_str(), MemoServ->nick.c_str(), ci->name.c_str(), mi->memos->size()); + } + } + } + } + else + { + NickCore *nc = NickAlias::Find(target)->nc; + + if (nc->HasExt("MEMO_RECEIVE")) + { + for (auto *na : *nc->aliases) + { + User *user = User::Find(na->nick, true); + if (user && user->IsIdentified()) + user->SendMessage(MemoServ, MEMO_NEW_MEMO_ARRIVED, source.c_str(), Config->StrictPrivmsg.c_str(), MemoServ->nick.c_str(), mi->memos->size()); + } + } + + /* let's get out the mail if set in the nickcore - certus */ + if (nc->HasExt("MEMO_MAIL")) + SendMemoMail(nc, mi, m); + } + + return MEMO_SUCCESS; + } + + void Check(User *u) override + { + const NickCore *nc = u->Account(); + if (!nc) + return; + + unsigned i = 0, end = nc->memos.memos->size(), newcnt = 0; + for (; i < end; ++i) + if (nc->memos.GetMemo(i)->unread) + ++newcnt; + if (newcnt > 0) + u->SendMessage(MemoServ, newcnt == 1 ? _("You have 1 new memo.") : _("You have %d new memos."), newcnt); + if (nc->memos.memomax > 0 && nc->memos.memos->size() >= static_cast<unsigned>(nc->memos.memomax)) + { + if (nc->memos.memos->size() > static_cast<unsigned>(nc->memos.memomax)) + u->SendMessage(MemoServ, _("You are over your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."), nc->memos.memomax); + else + u->SendMessage(MemoServ, _("You have reached your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."), nc->memos.memomax); + } + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &msnick = conf->GetModule(this)->Get<const Anope::string>("client"); + + if (msnick.empty()) + throw ConfigException(Module::name + ": <client> must be defined"); + + BotInfo *bi = BotInfo::Find(msnick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + msnick); + + MemoServ = bi; + } + + void OnNickCoreCreate(NickCore *nc) override + { + nc->memos.memomax = Config->GetModule(this)->Get<int>("maxmemos"); + } + + void OnCreateChan(ChannelInfo *ci) override + { + ci->memos.memomax = Config->GetModule(this)->Get<int>("maxmemos"); + } + + void OnBotDelete(BotInfo *bi) override + { + if (bi == MemoServ) + MemoServ = NULL; + } + + void OnNickIdentify(User *u) override + { + this->Check(u); + } + + void OnJoinChannel(User *u, Channel *c) override + { + if (c->ci && !c->ci->memos.memos->empty() && c->ci->AccessFor(u).HasPriv("MEMO")) + { + if (c->ci->memos.memos->size() == 1) + u->SendMessage(MemoServ, _("There is \002%zu\002 memo on channel %s."), c->ci->memos.memos->size(), c->ci->name.c_str()); + else + u->SendMessage(MemoServ, _("There are \002%zu\002 memos on channel %s."), c->ci->memos.memos->size(), c->ci->name.c_str()); + } + } + + void OnUserAway(User *u, const Anope::string &message) override + { + if (message.empty()) + this->Check(u); + } + + void OnNickUpdate(User *u) override + { + this->Check(u); + } + + void OnUserConnect(User *user, bool &exempt) override + { + this->Check(user); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!params.empty() || source.c || source.service != *MemoServ) + return EVENT_CONTINUE; + source.Reply(_("\002%s\002 is a utility allowing IRC users to send short\n" + "messages to other IRC users, whether they are online at\n" + "the time or not, or to channels(*). Both the sender's\n" + "nickname and the target nickname or channel must be\n" + "registered in order to send a memo.\n" + "%s's commands include:"), MemoServ->nick.c_str(), MemoServ->nick.c_str()); + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!params.empty() || source.c || source.service != *MemoServ) + return; + source.Reply(_(" \n" + "Type \002%s%s HELP \037command\037\002 for help on any of the\n" + "above commands."), Config->StrictPrivmsg.c_str(), MemoServ->nick.c_str()); + } +}; + +MODULE_INIT(MemoServCore) diff --git a/modules/memoserv/ms_cancel.cpp b/modules/memoserv/ms_cancel.cpp new file mode 100644 index 000000000..330b55926 --- /dev/null +++ b/modules/memoserv/ms_cancel.cpp @@ -0,0 +1,103 @@ +/* MemoServ 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 CommandMSCancel final + : public Command +{ +public: + CommandMSCancel(Module *creator) : Command(creator, "memoserv/cancel", 1, 1) + { + this->SetDesc(_("Cancel the last memo you sent")); + this->SetSyntax(_("{\037nick\037 | \037channel\037}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + const Anope::string &nname = params[0]; + + bool ischan; + MemoInfo *mi = MemoInfo::GetMemoInfo(nname, ischan); + + if (mi == NULL) + { + source.Reply(ischan ? CHAN_X_NOT_REGISTERED : NICK_X_NOT_REGISTERED, nname.c_str()); + return; + } + + ChannelInfo *ci = NULL; + NickAlias *na = NULL; + if (ischan) + { + ci = ChannelInfo::Find(nname); + + if (ci == NULL) + return; // can't happen + } + else + { + na = NickAlias::Find(nname); + + if (na == NULL) + return; // can't happen + } + + for (int i = mi->memos->size() - 1; i >= 0; --i) + { + Memo *m = mi->GetMemo(i); + + if (!m->unread) + continue; + + NickAlias *sender = NickAlias::Find(m->sender); + + if (sender && sender->nc == source.GetAccount()) + { + FOREACH_MOD(OnMemoDel, (ischan ? ci->name : na->nc->display, mi, m)); + mi->Del(i); + source.Reply(_("Last memo to \002%s\002 has been cancelled."), (ischan ? ci->name : na->nc->display).c_str()); + return; + } + } + + source.Reply(_("No memo was cancelable.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Cancels the last memo you sent to the given nick or channel,\n" + "provided it has not been read at the time you use the command.")); + return true; + } +}; + +class MSCancel final + : public Module +{ + CommandMSCancel commandmscancel; + +public: + MSCancel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmscancel(this) + { + } +}; + +MODULE_INIT(MSCancel) diff --git a/modules/memoserv/ms_check.cpp b/modules/memoserv/ms_check.cpp new file mode 100644 index 000000000..d3556379d --- /dev/null +++ b/modules/memoserv/ms_check.cpp @@ -0,0 +1,89 @@ +/* MemoServ 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 CommandMSCheck final + : public Command +{ +public: + CommandMSCheck(Module *creator) : Command(creator, "memoserv/check", 1, 1) + { + this->SetDesc(_("Checks if last memo to a nick was read")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + + const Anope::string &recipient = params[0]; + + bool found = false; + + const NickAlias *na = NickAlias::Find(recipient); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, recipient.c_str()); + return; + } + + MemoInfo *mi = &na->nc->memos; + + /* Okay, I know this looks strange but we want to get the LAST memo, so we + have to loop backwards */ + + for (unsigned i = mi->memos->size(); i > 0; --i) + { + Memo *m = mi->GetMemo(i - 1); + NickAlias *na2 = NickAlias::Find(m->sender); + + if (na2 != NULL && na2->nc == source.GetAccount()) + { + found = true; /* Yes, we've found the memo */ + + if (m->unread) + source.Reply(_("The last memo you sent to %s (sent on %s) has not yet been read."), na->nick.c_str(), Anope::strftime(m->time, source.GetAccount()).c_str()); + else + source.Reply(_("The last memo you sent to %s (sent on %s) has been read."), na->nick.c_str(), Anope::strftime(m->time, source.GetAccount()).c_str()); + break; + } + } + + if (!found) + source.Reply(_("Nick %s doesn't have a memo from you."), na->nick.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Checks whether the _last_ memo you sent to \037nick\037 has been read\n" + "or not. Note that this only works with nicks, not with channels.")); + return true; + } +}; + +class MSCheck final + : public Module +{ + CommandMSCheck commandmscheck; + +public: + MSCheck(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmscheck(this) + { + + } +}; + +MODULE_INIT(MSCheck) diff --git a/modules/memoserv/ms_del.cpp b/modules/memoserv/ms_del.cpp new file mode 100644 index 000000000..469fdf221 --- /dev/null +++ b/modules/memoserv/ms_del.cpp @@ -0,0 +1,162 @@ +/* MemoServ 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 MemoDelCallback final + : public NumberList +{ + CommandSource &source; + Command *cmd; + ChannelInfo *ci; + MemoInfo *mi; +public: + MemoDelCallback(CommandSource &_source, Command *c, ChannelInfo *_ci, MemoInfo *_mi, const Anope::string &list) : NumberList(list, true), source(_source), cmd(c), ci(_ci), mi(_mi) + { + } + + void HandleNumber(unsigned number) override + { + if (!number || number > mi->memos->size()) + return; + + FOREACH_MOD(OnMemoDel, (ci ? ci->name : source.nc->display, mi, mi->GetMemo(number - 1))); + + mi->Del(number - 1); + source.Reply(_("Memo %d has been deleted."), number); + if (ci) + Log(LOG_COMMAND, source, cmd, ci) << "on memo " << number; + } +}; + +class CommandMSDel final + : public Command +{ +public: + CommandMSDel(Module *creator) : Command(creator, "memoserv/del", 0, 2) + { + this->SetDesc(_("Delete a memo or memos")); + this->SetSyntax(_("[\037channel\037] {\037num\037 | \037list\037 | LAST | ALL}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + MemoInfo *mi; + ChannelInfo *ci = NULL; + Anope::string numstr = !params.empty() ? params[0] : "", chan; + + if (!numstr.empty() && numstr[0] == '#') + { + chan = numstr; + numstr = params.size() > 1 ? params[1] : ""; + + ci = ChannelInfo::Find(chan); + if (!ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + else if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + } + else + mi = &source.nc->memos; + if (numstr.empty() || (!isdigit(numstr[0]) && !numstr.equals_ci("ALL") && !numstr.equals_ci("LAST"))) + this->OnSyntaxError(source, numstr); + else if (mi->memos->empty()) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_MEMOS); + } + else + { + if (isdigit(numstr[0])) + { + MemoDelCallback list(source, this, ci, mi, numstr); + list.Process(); + } + else if (numstr.equals_ci("LAST")) + { + /* Delete last memo. */ + FOREACH_MOD(OnMemoDel, (ci ? ci->name : source.nc->display, mi, mi->GetMemo(mi->memos->size() - 1))); + mi->Del(mi->memos->size() - 1); + source.Reply(_("Memo %zu has been deleted."), mi->memos->size() + 1); + if (ci) + Log(LOG_COMMAND, source, this, ci) << "on LAST memo"; + } + else + { + /* Delete all memos. */ + for (unsigned i = mi->memos->size(); i > 0; --i) + { + FOREACH_MOD(OnMemoDel, (ci ? ci->name : source.nc->display, mi, mi->GetMemo(i))); + mi->Del(i - 1); + } + if (!chan.empty()) + { + source.Reply(_("All memos for channel %s have been deleted."), chan.c_str()); + Log(LOG_COMMAND, source, this, ci) << "on ALL memos"; + } + else + source.Reply(_("All of your memos have been deleted.")); + } + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deletes the specified memo or memos. You can supply\n" + "multiple memo numbers or ranges of numbers instead of a\n" + "single number, as in the second example below.\n" + " \n" + "If \002LAST\002 is given, the last memo will be deleted.\n" + "If \002ALL\002 is given, deletes all of your memos.\n" + " \n" + "Examples:\n" + " \n" + " \002DEL 1\002\n" + " Deletes your first memo.\n" + " \n" + " \002DEL 2-5,7-9\002\n" + " Deletes memos numbered 2 through 5 and 7 through 9.")); + return true; + } +}; + +class MSDel final + : public Module +{ + CommandMSDel commandmsdel; + +public: + MSDel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmsdel(this) + { + + } +}; + +MODULE_INIT(MSDel) diff --git a/modules/memoserv/ms_ignore.cpp b/modules/memoserv/ms_ignore.cpp new file mode 100644 index 000000000..c67c1b550 --- /dev/null +++ b/modules/memoserv/ms_ignore.cpp @@ -0,0 +1,134 @@ +/* MemoServ 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 CommandMSIgnore final + : public Command +{ +public: + CommandMSIgnore(Module *creator) : Command(creator, "memoserv/ignore", 1, 3) + { + this->SetDesc(_("Manage the memo ignore list")); + this->SetSyntax(_("[\037channel\037] ADD \037entry\037")); + this->SetSyntax(_("[\037channel\037] DEL \037entry\037")); + this->SetSyntax(_("[\037channel\037] LIST")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + Anope::string channel = params[0]; + Anope::string command = (params.size() > 1 ? params[1] : ""); + Anope::string param = (params.size() > 2 ? params[2] : ""); + + if (channel[0] != '#') + { + param = command; + command = channel; + channel = source.GetNick(); + } + + bool ischan; + MemoInfo *mi = MemoInfo::GetMemoInfo(channel, ischan); + ChannelInfo *ci = ChannelInfo::Find(channel); + if (!mi) + source.Reply(ischan ? CHAN_X_NOT_REGISTERED : _(NICK_X_NOT_REGISTERED), channel.c_str()); + else if (ischan && !source.AccessFor(ci).HasPriv("MEMO")) + source.Reply(ACCESS_DENIED); + else if (command.equals_ci("ADD") && !param.empty()) + { + if (mi->ignores.size() >= Config->GetModule(this->owner)->Get<unsigned>("max", "32")) + { + source.Reply(_("Sorry, the memo ignore list for \002%s\002 is full."), channel.c_str()); + return; + } + + if (std::find(mi->ignores.begin(), mi->ignores.end(), param.ci_str()) == mi->ignores.end()) + { + mi->ignores.emplace_back(param.ci_str()); + source.Reply(_("\002%s\002 added to ignore list."), param.c_str()); + } + else + source.Reply(_("\002%s\002 is already on the ignore list."), param.c_str()); + } + else if (command.equals_ci("DEL") && !param.empty()) + { + std::vector<Anope::string>::iterator it = std::find(mi->ignores.begin(), mi->ignores.end(), param.ci_str()); + + if (it != mi->ignores.end()) + { + mi->ignores.erase(it); + source.Reply(_("\002%s\002 removed from the ignore list."), param.c_str()); + } + else + source.Reply(_("\002%s\002 is not on the ignore list."), param.c_str()); + } + else if (command.equals_ci("LIST")) + { + if (mi->ignores.empty()) + source.Reply(_("Memo ignore list is empty.")); + else + { + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Mask")); + for (const auto &ignore : mi->ignores) + { + ListFormatter::ListEntry entry; + entry["Mask"] = ignore; + list.AddEntry(entry); + } + + source.Reply(_("Ignore list:")); + + std::vector<Anope::string> replies; + list.Process(replies); + + for (const auto &reply : replies) + source.Reply(reply); + } + } + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to ignore users by nick or host from memoing\n" + "you or a channel. If someone on the memo ignore list tries\n" + "to memo you or a channel, they will not be told that you have\n" + "them ignored.")); + return true; + } +}; + +class MSIgnore final + : public Module +{ + CommandMSIgnore commandmsignore; + +public: + MSIgnore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmsignore(this) + { + } +}; + +MODULE_INIT(MSIgnore) diff --git a/modules/memoserv/ms_info.cpp b/modules/memoserv/ms_info.cpp new file mode 100644 index 000000000..c76a446cd --- /dev/null +++ b/modules/memoserv/ms_info.cpp @@ -0,0 +1,235 @@ +/* MemoServ 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 CommandMSInfo final + : public Command +{ +public: + CommandMSInfo(Module *creator) : Command(creator, "memoserv/info", 0, 1) + { + this->SetDesc(_("Displays information about your memos")); + this->SetSyntax(_("[\037nick\037 | \037channel\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + NickCore *nc = source.nc; + const MemoInfo *mi; + const NickAlias *na = NULL; + ChannelInfo *ci = NULL; + const Anope::string &nname = !params.empty() ? params[0] : ""; + bool hardmax; + + if (!nname.empty() && nname[0] != '#' && source.HasPriv("memoserv/info")) + { + na = NickAlias::Find(nname); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, nname.c_str()); + return; + } + mi = &na->nc->memos; + hardmax = na->nc->HasExt("MEMO_HARDMAX"); + } + else if (!nname.empty() && nname[0] == '#') + { + ci = ChannelInfo::Find(nname); + if (!ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, nname.c_str()); + return; + } + else if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + hardmax = ci->HasExt("MEMO_HARDMAX"); + } + else if (!nname.empty()) /* It's not a chan and we aren't services admin */ + { + source.Reply(ACCESS_DENIED); + return; + } + else + { + mi = &nc->memos; + hardmax = nc->HasExt("MEMO_HARDMAX"); + } + + if (!nname.empty() && (ci || na->nc != nc)) + { + if (mi->memos->empty()) + source.Reply(_("%s currently has no memos."), nname.c_str()); + else if (mi->memos->size() == 1) + { + if (mi->GetMemo(0)->unread) + source.Reply(_("%s currently has \0021\002 memo, and it has not yet been read."), nname.c_str()); + else + source.Reply(_("%s currently has \0021\002 memo."), nname.c_str()); + } + else + { + size_t count = 0; + for (size_t i = 0; i < mi->memos->size(); ++i) + if (mi->GetMemo(i)->unread) + ++count; + + if (count == mi->memos->size()) + source.Reply(_("%s currently has \002%zu\002 memos; all of them are unread."), nname.c_str(), count); + else if (!count) + source.Reply(_("%s currently has \002%zu\002 memos."), nname.c_str(), mi->memos->size()); + else if (count == 1) + source.Reply(_("%s currently has \002%zu\002 memos, of which \0021\002 is unread."), nname.c_str(), mi->memos->size()); + else + source.Reply(_("%s currently has \002%zu\002 memos, of which \002%zu\002 are unread."), nname.c_str(), mi->memos->size(), count); + } + if (!mi->memomax) + { + if (hardmax) + source.Reply(_("%s's memo limit is \002%d\002, and may not be changed."), nname.c_str(), mi->memomax); + else + source.Reply(_("%s's memo limit is \002%d\002."), nname.c_str(), mi->memomax); + } + else if (mi->memomax > 0) + { + if (hardmax) + source.Reply(_("%s's memo limit is \002%d\002, and may not be changed."), nname.c_str(), mi->memomax); + else + source.Reply(_("%s's memo limit is \002%d\002."), nname.c_str(), mi->memomax); + } + else + source.Reply(_("%s has no memo limit."), nname.c_str()); + + /* I ripped this code out of ircservices 4.4.5, since I didn't want + to rewrite the whole thing (it pisses me off). */ + if (na) + { + if (na->nc->HasExt("MEMO_RECEIVE") && na->nc->HasExt("MEMO_SIGNON")) + source.Reply(_("%s is notified of new memos at logon and when they arrive."), nname.c_str()); + else if (na->nc->HasExt("MEMO_RECEIVE")) + source.Reply(_("%s is notified when new memos arrive."), nname.c_str()); + else if (na->nc->HasExt("MEMO_SIGNON")) + source.Reply(_("%s is notified of new memos at logon."), nname.c_str()); + else + source.Reply(_("%s is not notified of new memos."), nname.c_str()); + } + } + else /* !nname || (!ci || na->nc == nc) */ + { + if (mi->memos->empty()) + source.Reply(_("You currently have no memos.")); + else if (mi->memos->size() == 1) + { + if (mi->GetMemo(0)->unread) + source.Reply(_("You currently have \0021\002 memo, and it has not yet been read.")); + else + source.Reply(_("You currently have \0021\002 memo.")); + } + else + { + size_t count = 0; + for (size_t i = 0; i < mi->memos->size(); ++i) + if (mi->GetMemo(i)->unread) + ++count; + + if (count == mi->memos->size()) + source.Reply(_("You currently have \002%zu\002 memos; all of them are unread."), count); + else if (!count) + source.Reply(_("You currently have \002%zu\002 memos."), mi->memos->size()); + else if (count == 1) + source.Reply(_("You currently have \002%zu\002 memos, of which \0021\002 is unread."), mi->memos->size()); + else + source.Reply(_("You currently have \002%zu\002 memos, of which \002%zu\002 are unread."), mi->memos->size(), count); + } + + if (!mi->memomax) + { + if (!source.IsServicesOper() && hardmax) + source.Reply(_("Your memo limit is \0020\002; you will not receive any new memos. You cannot change this limit.")); + else + source.Reply(_("Your memo limit is \0020\002; you will not receive any new memos.")); + } + else if (mi->memomax > 0) + { + if (!source.IsServicesOper() && hardmax) + source.Reply(_("Your memo limit is \002%d\002, and may not be changed."), mi->memomax); + else + source.Reply(_("Your memo limit is \002%d\002."), mi->memomax); + } + else + source.Reply(_("You have no limit on the number of memos you may keep.")); + + bool memo_mail = nc->HasExt("MEMO_MAIL"); + if (nc->HasExt("MEMO_RECEIVE") && nc->HasExt("MEMO_SIGNON")) + { + if (memo_mail) + source.Reply(_("You will be notified of new memos at logon and when they arrive, and by mail when they arrive.")); + else + source.Reply(_("You will be notified of new memos at logon and when they arrive.")); + } + else if (nc->HasExt("MEMO_RECEIVE")) + { + if (memo_mail) + source.Reply(_("You will be notified by message and by mail when new memos arrive.")); + else + source.Reply(_("You will be notified when new memos arrive.")); + } + else if (nc->HasExt("MEMO_SIGNON")) + { + if (memo_mail) + source.Reply(_("You will be notified of new memos at logon, and by mail when they arrive.")); + else + source.Reply(_("You will be notified of new memos at logon.")); + } + else + { + source.Reply(_("You will not be notified of new memos.")); + } + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Without a parameter, displays information on the number of\n" + "memos you have, how many of them are unread, and how many\n" + "total memos you can receive.\n" + " \n" + "With a channel parameter, displays the same information for\n" + "the given channel.\n" + " \n" + "With a nickname parameter, displays the same information\n" + "for the given nickname. This is limited to \002Services\002\n" + "\002Operators\002.")); + + return true; + } +}; + +class MSInfo final + : public Module +{ + CommandMSInfo commandmsinfo; + +public: + MSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmsinfo(this) + { + + } +}; + +MODULE_INIT(MSInfo) diff --git a/modules/memoserv/ms_list.cpp b/modules/memoserv/ms_list.cpp new file mode 100644 index 000000000..127cfdaaf --- /dev/null +++ b/modules/memoserv/ms_list.cpp @@ -0,0 +1,167 @@ +/* MemoServ 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 CommandMSList final + : public Command +{ +public: + CommandMSList(Module *creator) : Command(creator, "memoserv/list", 0, 2) + { + this->SetDesc(_("List your memos")); + this->SetSyntax(_("[\037channel\037] [\037list\037 | NEW]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + + Anope::string param = !params.empty() ? params[0] : "", chan; + ChannelInfo *ci = NULL; + const MemoInfo *mi; + + if (!param.empty() && param[0] == '#') + { + chan = param; + param = params.size() > 1 ? params[1] : ""; + + ci = ChannelInfo::Find(chan); + if (!ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + else if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + } + else + mi = &source.nc->memos; + + if (!param.empty() && !isdigit(param[0]) && !param.equals_ci("NEW")) + this->OnSyntaxError(source, param); + else if (!mi->memos->size()) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_MEMOS); + } + else + { + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Number")).AddColumn(_("Sender")).AddColumn(_("Date/Time")); + + if (!param.empty() && isdigit(param[0])) + { + class MemoListCallback final + : public NumberList + { + ListFormatter &list; + CommandSource &source; + const MemoInfo *mi; + public: + MemoListCallback(ListFormatter &_list, CommandSource &_source, const MemoInfo *_mi, const Anope::string &numlist) : NumberList(numlist, false), list(_list), source(_source), mi(_mi) + { + } + + void HandleNumber(unsigned number) override + { + if (!number || number > mi->memos->size()) + return; + + const Memo *m = mi->GetMemo(number - 1); + + ListFormatter::ListEntry entry; + entry["Number"] = (m->unread ? "* " : " ") + stringify(number); + entry["Sender"] = m->sender; + entry["Date/Time"] = Anope::strftime(m->time, source.GetAccount()); + this->list.AddEntry(entry); + } + } + mlc(list, source, mi, param); + mlc.Process(); + } + else + { + if (!param.empty()) + { + unsigned i, end; + for (i = 0, end = mi->memos->size(); i < end; ++i) + if (mi->GetMemo(i)->unread) + break; + if (i == end) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_NEW_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_NEW_MEMOS); + return; + } + } + + for (unsigned i = 0, end = mi->memos->size(); i < end; ++i) + { + if (!param.empty() && !mi->GetMemo(i)->unread) + continue; + + const Memo *m = mi->GetMemo(i); + + ListFormatter::ListEntry entry; + entry["Number"] = (m->unread ? "* " : " ") + stringify(i + 1); + entry["Sender"] = m->sender; + entry["Date/Time"] = Anope::strftime(m->time, source.GetAccount()); + list.AddEntry(entry); + } + } + + std::vector<Anope::string> replies; + list.Process(replies); + + source.Reply(_("Memos for %s:"), ci ? ci->name.c_str() : source.GetNick().c_str()); + for (const auto &reply : replies) + source.Reply(reply); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists any memos you currently have. With \002NEW\002, lists only\n" + "new (unread) memos. Unread memos are marked with a \"*\"\n" + "to the left of the memo number. You can also specify a list\n" + "of numbers, as in the example below:\n" + " \002LIST 2-5,7-9\002\n" + " Lists memos numbered 2 through 5 and 7 through 9.")); + return true; + } +}; + +class MSList final + : public Module +{ + CommandMSList commandmslist; + +public: + MSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmslist(this) + { + + } +}; + +MODULE_INIT(MSList) diff --git a/modules/memoserv/ms_read.cpp b/modules/memoserv/ms_read.cpp new file mode 100644 index 000000000..5e9c8fd83 --- /dev/null +++ b/modules/memoserv/ms_read.cpp @@ -0,0 +1,227 @@ +/* MemoServ 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" + +static ServiceReference<MemoServService> MemoServService("MemoServService", "MemoServ"); + +static void rsend_notify(CommandSource &source, MemoInfo *mi, Memo *m, const Anope::string &targ) +{ + /* Only send receipt if memos are allowed */ + if (MemoServService && !Anope::ReadOnly) + { + /* Get nick alias for sender */ + const NickAlias *na = NickAlias::Find(m->sender); + + if (!na) + return; + + /* Get nick core for sender */ + const NickCore *nc = na->nc; + + if (!nc) + return; + + /* Text of the memo varies if the recipient was a + nick or channel */ + Anope::string text = Anope::printf(Language::Translate(na->nc, _("\002[auto-memo]\002 The memo you sent to %s has been viewed.")), targ.c_str()); + + /* Send notification */ + MemoServService->Send(source.GetNick(), m->sender, text, true); + + /* Notify recipient of the memo that a notification has + been sent to the sender */ + source.Reply(_("A notification memo has been sent to %s informing them you have\n" + "read their memo."), nc->display.c_str()); + } + + /* Remove receipt flag from the original memo */ + m->receipt = false; +} + +class MemoListCallback final + : public NumberList +{ + CommandSource &source; + MemoInfo *mi; + const ChannelInfo *ci; + bool found; +public: + MemoListCallback(CommandSource &_source, MemoInfo *_mi, const ChannelInfo *_ci, const Anope::string &numlist) : NumberList(numlist, false), source(_source), mi(_mi), ci(_ci) + { + found = false; + } + + ~MemoListCallback() override + { + if (!found) + source.Reply(_("No memos to display.")); + } + + void HandleNumber(unsigned number) override + { + if (!number || number > mi->memos->size()) + return; + + MemoListCallback::DoRead(source, mi, ci, number - 1); + found = true; + } + + static void DoRead(CommandSource &source, MemoInfo *mi, const ChannelInfo *ci, unsigned index) + { + Memo *m = mi->GetMemo(index); + if (!m) + return; + + if (ci) + source.Reply(_("Memo %d from %s (%s)."), index + 1, m->sender.c_str(), Anope::strftime(m->time, source.GetAccount()).c_str()); + else + source.Reply(_("Memo %d from %s (%s)."), index + 1, m->sender.c_str(), Anope::strftime(m->time, source.GetAccount()).c_str()); + + BotInfo *bi; + Anope::string cmd; + if (Command::FindCommandFromService("memoserv/del", bi, cmd)) + { + if (ci) + source.Reply(_("To delete, type: \002%s%s %s %s %d\002"), Config->StrictPrivmsg.c_str(), bi->nick.c_str(), cmd.c_str(), ci->name.c_str(), index + 1); + else + source.Reply(_("To delete, type: \002%s%s %s %d\002"), Config->StrictPrivmsg.c_str(), bi->nick.c_str(), cmd.c_str(), index + 1); + } + + source.Reply("%s", m->text.c_str()); + m->unread = false; + + /* Check if a receipt notification was requested */ + if (m->receipt) + rsend_notify(source, mi, m, ci ? ci->name : source.GetNick()); + } +}; + +class CommandMSRead final + : public Command +{ +public: + CommandMSRead(Module *creator) : Command(creator, "memoserv/read", 1, 2) + { + this->SetDesc(_("Read a memo or memos")); + this->SetSyntax(_("[\037channel\037] {\037num\037 | \037list\037 | LAST | NEW | ALL}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + + MemoInfo *mi; + ChannelInfo *ci = NULL; + Anope::string numstr = params[0], chan; + + if (!numstr.empty() && numstr[0] == '#') + { + chan = numstr; + numstr = params.size() > 1 ? params[1] : ""; + + ci = ChannelInfo::Find(chan); + if (!ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + else if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + } + else + mi = &source.nc->memos; + + if (numstr.empty() || (!numstr.equals_ci("LAST") && !numstr.equals_ci("NEW") && !numstr.equals_ci("ALL") && numstr.find_first_not_of("0123456789.,-") != Anope::string::npos)) + this->OnSyntaxError(source, numstr); + else if (mi->memos->empty()) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_MEMOS); + } + else + { + int i, end; + + if (numstr.equals_ci("NEW")) + { + int readcount = 0; + for (i = 0, end = mi->memos->size(); i < end; ++i) + if (mi->GetMemo(i)->unread) + { + MemoListCallback::DoRead(source, mi, ci, i); + ++readcount; + } + if (!readcount) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_NEW_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_NEW_MEMOS); + } + } + else if (numstr.equals_ci("LAST")) + { + for (i = 0, end = mi->memos->size() - 1; i < end; ++i); + MemoListCallback::DoRead(source, mi, ci, i); + } + else if (numstr.equals_ci("ALL")) + { + for (i = 0, end = mi->memos->size(); i < end; ++i) + { + MemoListCallback::DoRead(source, mi, ci, i); + } + } + else /* number[s] */ + { + MemoListCallback list(source, mi, ci, numstr); + list.Process(); + } + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends you the text of the memos specified. If LAST is\n" + "given, sends you the memo you most recently received. If\n" + "NEW is given, sends you all of your new memos. If ALL is\n" + "given, sends you all of your memos. Otherwise, sends you\n" + "memo number \037num\037. You can also give a list of numbers,\n" + "as in this example:\n" + " \n" + " \002READ 2-5,7-9\002\n" + " Displays memos numbered 2 through 5 and 7 through 9.")); + return true; + } +}; + +class MSRead final + : public Module +{ + CommandMSRead commandmsread; + +public: + MSRead(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmsread(this) + { + + } +}; + +MODULE_INIT(MSRead) diff --git a/modules/memoserv/ms_rsend.cpp b/modules/memoserv/ms_rsend.cpp new file mode 100644 index 000000000..a5de58e56 --- /dev/null +++ b/modules/memoserv/ms_rsend.cpp @@ -0,0 +1,106 @@ +/* MemoServ 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" + +namespace +{ + ServiceReference<MemoServService> memoserv("MemoServService", "MemoServ"); +} + +class CommandMSRSend final + : public Command +{ +public: + CommandMSRSend(Module *creator) : Command(creator, "memoserv/rsend", 2, 2) + { + this->SetDesc(_("Sends a memo and requests a read receipt")); + this->SetSyntax(_("{\037nick\037 | \037channel\037} \037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!memoserv) + return; + + if (Anope::ReadOnly && !source.IsOper()) + { + source.Reply(MEMO_SEND_DISABLED); + return; + } + + const Anope::string &nick = params[0]; + const Anope::string &text = params[1]; + const NickAlias *na = NULL; + + /* prevent user from rsend to themselves */ + if ((na = NickAlias::Find(nick)) && na->nc == source.GetAccount()) + { + source.Reply(_("You can not request a receipt when sending a memo to yourself.")); + return; + } + + if (Config->GetModule(this->owner)->Get<bool>("operonly") && !source.IsServicesOper()) + source.Reply(ACCESS_DENIED); + else + { + MemoServService::MemoResult result = memoserv->Send(source.GetNick(), nick, text); + if (result == MemoServService::MEMO_INVALID_TARGET) + source.Reply(_("\002%s\002 is not a registered unforbidden nick or channel."), nick.c_str()); + else if (result == MemoServService::MEMO_TOO_FAST) + source.Reply(_("Please wait %lu seconds before using the %s command again."), Config->GetModule("memoserv")->Get<unsigned long>("senddelay"), source.command.c_str()); + else if (result == MemoServService::MEMO_TARGET_FULL) + source.Reply(_("Sorry, %s currently has too many memos and cannot receive more."), nick.c_str()); + else + { + source.Reply(_("Memo sent to \002%s\002."), nick.c_str()); + + bool ischan; + MemoInfo *mi = MemoInfo::GetMemoInfo(nick, ischan); + if (mi == NULL) + throw CoreException("NULL mi in ms_rsend"); + Memo *m = (mi->memos->size() ? mi->GetMemo(mi->memos->size() - 1) : NULL); + if (m != NULL) + m->receipt = true; + } + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends the named \037nick\037 or \037channel\037 a memo containing\n" + "\037memo-text\037. When sending to a nickname, the recipient will\n" + "receive a notice that they have a new memo. The target\n" + "nickname/channel must be registered.\n" + "Once the memo is read by its recipient, an automatic notification\n" + "memo will be sent to the sender informing them that the memo\n" + "has been read.")); + return true; + } +}; + +class MSRSend final + : public Module +{ + CommandMSRSend commandmsrsend; + +public: + MSRSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmsrsend(this) + { + if (!memoserv) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSRSend) diff --git a/modules/memoserv/ms_send.cpp b/modules/memoserv/ms_send.cpp new file mode 100644 index 000000000..1335f1ad4 --- /dev/null +++ b/modules/memoserv/ms_send.cpp @@ -0,0 +1,90 @@ +/* MemoServ 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" + +namespace +{ + ServiceReference<MemoServService> memoserv("MemoServService", "MemoServ"); +} + +class CommandMSSend final + : public Command +{ +public: + CommandMSSend(Module *creator) : Command(creator, "memoserv/send", 2, 2) + { + this->SetDesc(_("Send a memo to a nick or channel")); + this->SetSyntax(_("{\037nick\037 | \037channel\037} \037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!memoserv) + return; + + const Anope::string &nick = params[0]; + const Anope::string &text = params[1]; + + if (Anope::ReadOnly && !source.IsOper()) + { + source.Reply(MEMO_SEND_DISABLED); + return; + } + + if (source.GetAccount()->HasExt("UNCONFIRMED")) + { + source.Reply(_("You must confirm your account before you may send a memo.")); + return; + } + + MemoServService::MemoResult result = memoserv->Send(source.GetNick(), nick, text); + if (result == MemoServService::MEMO_SUCCESS) + { + source.Reply(_("Memo sent to \002%s\002."), nick.c_str()); + Log(LOG_COMMAND, source, this) << "to send a memo to " << nick; + } + else if (result == MemoServService::MEMO_INVALID_TARGET) + source.Reply(_("\002%s\002 is not a registered unforbidden nick or channel."), nick.c_str()); + else if (result == MemoServService::MEMO_TOO_FAST) + source.Reply(_("Please wait %lu seconds before using the %s command again."), Config->GetModule("memoserv")->Get<unsigned long>("senddelay"), source.command.c_str()); + else if (result == MemoServService::MEMO_TARGET_FULL) + source.Reply(_("Sorry, %s currently has too many memos and cannot receive more."), nick.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends the named \037nick\037 or \037channel\037 a memo containing\n" + "\037memo-text\037. When sending to a nickname, the recipient will\n" + "receive a notice that they have a new memo. The target\n" + "nickname/channel must be registered.")); + return true; + } +}; + +class MSSend final + : public Module +{ + CommandMSSend commandmssend; + +public: + MSSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmssend(this) + { + + if (!memoserv) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSSend) diff --git a/modules/memoserv/ms_sendall.cpp b/modules/memoserv/ms_sendall.cpp new file mode 100644 index 000000000..c04ff7a3f --- /dev/null +++ b/modules/memoserv/ms_sendall.cpp @@ -0,0 +1,70 @@ +/* MemoServ 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" + +namespace +{ + ServiceReference<MemoServService> memoserv("MemoServService", "MemoServ"); +} + +class CommandMSSendAll final + : public Command +{ +public: + CommandMSSendAll(Module *creator) : Command(creator, "memoserv/sendall", 1, 1) + { + this->SetDesc(_("Send a memo to all registered users")); + this->SetSyntax(_("\037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!memoserv) + return; + + const Anope::string &text = params[0]; + + Log(LOG_ADMIN, source, this) << "to send " << text; + + for (const auto &[_, nc] : *NickCoreList) + { + if (nc != source.nc) + memoserv->Send(source.GetNick(), nc->display, text); + } + + source.Reply(_("A massmemo has been sent to all registered users.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends all registered users a memo containing \037memo-text\037.")); + return true; + } +}; + +class MSSendAll final + : public Module +{ + CommandMSSendAll commandmssendall; + +public: + MSSendAll(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmssendall(this) + { + if (!memoserv) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSSendAll) diff --git a/modules/memoserv/ms_set.cpp b/modules/memoserv/ms_set.cpp new file mode 100644 index 000000000..83b28fb20 --- /dev/null +++ b/modules/memoserv/ms_set.cpp @@ -0,0 +1,317 @@ +/* MemoServ 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 CommandMSSet final + : public Command +{ +private: + void DoNotify(CommandSource &source, const std::vector<Anope::string> ¶ms, MemoInfo *mi) + { + const Anope::string ¶m = params[1]; + NickCore *nc = source.nc; + BotInfo *MemoServ = Config->GetClient("MemoServ"); + + if (!MemoServ) + return; + + if (param.equals_ci("ON")) + { + nc->Extend<bool>("MEMO_SIGNON"); + nc->Extend<bool>("MEMO_RECEIVE"); + source.Reply(_("%s will now notify you of memos when you log on and when they are sent to you."), MemoServ->nick.c_str()); + } + else if (param.equals_ci("LOGON")) + { + nc->Extend<bool>("MEMO_SIGNON"); + nc->Shrink<bool>("MEMO_RECEIVE"); + source.Reply(_("%s will now notify you of memos when you log on or unset /AWAY."), MemoServ->nick.c_str()); + } + else if (param.equals_ci("NEW")) + { + nc->Shrink<bool>("MEMO_SIGNON"); + nc->Extend<bool>("MEMO_RECEIVE"); + source.Reply(_("%s will now notify you of memos when they are sent to you."), MemoServ->nick.c_str()); + } + else if (param.equals_ci("MAIL")) + { + if (!nc->email.empty()) + { + nc->Extend<bool>("MEMO_MAIL"); + source.Reply(_("You will now be informed about new memos via email.")); + } + else + source.Reply(_("There's no email address set for your nick.")); + } + else if (param.equals_ci("NOMAIL")) + { + nc->Shrink<bool>("MEMO_MAIL"); + source.Reply(_("You will no longer be informed via email.")); + } + else if (param.equals_ci("OFF")) + { + nc->Shrink<bool>("MEMO_SIGNON"); + nc->Shrink<bool>("MEMO_RECEIVE"); + nc->Shrink<bool>("MEMO_MAIL"); + source.Reply(_("%s will not send you any notification of memos."), MemoServ->nick.c_str()); + } + else + this->OnSyntaxError(source, ""); + } + + void DoLimit(CommandSource &source, const std::vector<Anope::string> ¶ms, MemoInfo *mi) + { + + Anope::string p1 = params[1]; + Anope::string p2 = params.size() > 2 ? params[2] : ""; + Anope::string p3 = params.size() > 3 ? params[3] : ""; + Anope::string user, chan; + int16_t limit; + NickCore *nc = source.nc; + ChannelInfo *ci = NULL; + bool is_servadmin = source.HasPriv("memoserv/set-limit"); + + if (p1[0] == '#') + { + chan = p1; + p1 = p2; + p2 = p3; + p3 = params.size() > 4 ? params[4] : ""; + + ci = ChannelInfo::Find(chan); + if (!ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + else if (!is_servadmin && !source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + } + if (is_servadmin) + { + if (!p2.empty() && !p2.equals_ci("HARD") && chan.empty()) + { + const NickAlias *na; + if (!(na = NickAlias::Find(p1))) + { + source.Reply(NICK_X_NOT_REGISTERED, p1.c_str()); + return; + } + user = p1; + mi = &na->nc->memos; + nc = na->nc; + p1 = p2; + p2 = p3; + } + else if (p1.empty() || (!p1.is_pos_number_only() && !p1.equals_ci("NONE")) || (!p2.empty() && !p2.equals_ci("HARD"))) + { + this->OnSyntaxError(source, ""); + return; + } + if (!chan.empty()) + { + if (!p2.empty()) + ci->Extend<bool>("MEMO_HARDMAX"); + else + ci->Shrink<bool>("MEMO_HARDMAX"); + } + else + { + if (!p2.empty()) + nc->Extend<bool>("MEMO_HARDMAX"); + else + nc->Shrink<bool>("MEMO_HARDMAX"); + } + limit = -1; + try + { + limit = convertTo<int16_t>(p1); + } + catch (const ConvertException &) { } + } + else + { + if (p1.empty() || !p2.empty() || !isdigit(p1[0])) + { + this->OnSyntaxError(source, ""); + return; + } + if (!chan.empty() && ci->HasExt("MEMO_HARDMAX")) + { + source.Reply(_("The memo limit for %s may not be changed."), chan.c_str()); + return; + } + else if (chan.empty() && nc->HasExt("MEMO_HARDMAX")) + { + source.Reply(_("You are not permitted to change your memo limit.")); + return; + } + int max_memos = Config->GetModule("memoserv")->Get<int>("maxmemos"); + limit = -1; + try + { + limit = convertTo<int16_t>(p1); + } + catch (const ConvertException &) { } + /* The first character is a digit, but we could still go negative + * from overflow... watch out! */ + if (limit < 0 || (max_memos > 0 && limit > max_memos)) + { + if (!chan.empty()) + source.Reply(_("You cannot set the memo limit for %s higher than %d."), chan.c_str(), max_memos); + else + source.Reply(_("You cannot set your memo limit higher than %d."), max_memos); + return; + } + } + mi->memomax = limit; + if (limit > 0) + { + if (chan.empty() && nc == source.nc) + source.Reply(_("Your memo limit has been set to \002%d\002."), limit); + else + source.Reply(_("Memo limit for %s set to \002%d\002."), !chan.empty() ? chan.c_str() : user.c_str(), limit); + } + else if (!limit) + { + if (chan.empty() && nc == source.nc) + source.Reply(_("You will no longer be able to receive memos.")); + else + source.Reply(_("Memo limit for %s set to \0020\002."), !chan.empty() ? chan.c_str() : user.c_str()); + } + else + { + if (chan.empty() && nc == source.nc) + source.Reply(_("Your memo limit has been disabled.")); + else + source.Reply(_("Memo limit \002disabled\002 for %s."), !chan.empty() ? chan.c_str() : user.c_str()); + } + return; + } +public: + CommandMSSet(Module *creator) : Command(creator, "memoserv/set", 2, 5) + { + this->SetDesc(_("Set options related to memos")); + this->SetSyntax(_("\037option\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &cmd = params[0]; + MemoInfo *mi = &source.nc->memos; + + if (Anope::ReadOnly) + source.Reply(_("Sorry, memo option setting is temporarily disabled.")); + else if (cmd.equals_ci("NOTIFY")) + return this->DoNotify(source, params, mi); + else if (cmd.equals_ci("LIMIT")) + return this->DoLimit(source, params, mi); + else + { + this->OnSyntaxError(source, ""); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.empty()) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets various memo options. \037option\037 can be one of:\n" + " \n" + " NOTIFY Changes when you will be notified about\n" + " new memos (only for nicknames)\n" + " LIMIT Sets the maximum number of memos you can\n" + " receive\n" + " \n" + "Type \002%s%s HELP %s \037option\037\002 for more information\n" + "on a specific option."), Config->StrictPrivmsg.c_str(), source.service->nick.c_str(), source.command.c_str()); + } + else if (subcommand.equals_ci("NOTIFY")) + source.Reply(_("Syntax: \002NOTIFY {ON | LOGON | NEW | MAIL | NOMAIL | OFF}\002\n" + " \n" + "Changes when you will be notified about new memos:\n" + " \n" + " ON You will be notified of memos when you log on,\n" + " when you unset /AWAY, and when they are sent\n" + " to you.\n" + " LOGON You will only be notified of memos when you log\n" + " on or when you unset /AWAY.\n" + " NEW You will only be notified of memos when they\n" + " are sent to you.\n" + " MAIL You will be notified of memos by email as well as\n" + " any other settings you have.\n" + " NOMAIL You will not be notified of memos by email.\n" + " OFF You will not receive any notification of memos.\n" + " \n" + "\002ON\002 is essentially \002LOGON\002 and \002NEW\002 combined.")); + else if (subcommand.equals_ci("LIMIT")) + { + int max_memos = Config->GetModule("memoserv")->Get<int>("maxmemos"); + if (source.IsServicesOper()) + source.Reply(_("Syntax: \002LIMIT [\037user\037 | \037channel\037] {\037limit\037 | NONE} [HARD]\002\n" + " \n" + "Sets the maximum number of memos a user or channel is\n" + "allowed to have. Setting the limit to 0 prevents the user\n" + "from receiving any memos; setting it to \002NONE\002 allows the\n" + "user to receive and keep as many memos as they want. If\n" + "you do not give a nickname or channel, your own limit is\n" + "set.\n" + " \n" + "Adding \002HARD\002 prevents the user from changing the limit. Not\n" + "adding \002HARD\002 has the opposite effect, allowing the user to\n" + "change the limit (even if a previous limit was set with\n" + "\002HARD\002).\n" + " \n" + "This use of the \002SET LIMIT\002 command is limited to \002Services\002\n" + "\002Operators\002. Other users may only enter a limit for themselves\n" + "or a channel on which they have such privileges, may not\n" + "remove their limit, may not set a limit above %d, and may\n" + "not set a hard limit."), max_memos); + else + source.Reply(_("Syntax: \002LIMIT [\037channel\037] \037limit\037\002\n" + " \n" + "Sets the maximum number of memos you (or the given channel)\n" + "are allowed to have. If you set this to 0, no one will be\n" + "able to send any memos to you. However, you cannot set\n" + "this any higher than %d."), max_memos); + } + else + return false; + + return true; + } +}; + +class MSSet final + : public Module +{ + CommandMSSet commandmsset; + SerializableExtensibleItem<bool> memo_signon, memo_receive, memo_mail, memo_hardmax; + +public: + MSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmsset(this), memo_signon(this, "MEMO_SIGNON"), memo_receive(this, "MEMO_RECEIVE"), memo_mail(this, "MEMO_MAIL"), + memo_hardmax(this, "MEMO_HARDMAX") + { + + } +}; + +MODULE_INIT(MSSet) diff --git a/modules/memoserv/ms_staff.cpp b/modules/memoserv/ms_staff.cpp new file mode 100644 index 000000000..cbd27061d --- /dev/null +++ b/modules/memoserv/ms_staff.cpp @@ -0,0 +1,67 @@ +/* MemoServ 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" + +namespace +{ + ServiceReference<MemoServService> memoserv("MemoServService", "MemoServ"); +} + +class CommandMSStaff final + : public Command +{ +public: + CommandMSStaff(Module *creator) : Command(creator, "memoserv/staff", 1, 1) + { + this->SetDesc(_("Send a memo to all opers/admins")); + this->SetSyntax(_("\037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!memoserv) + return; + + const Anope::string &text = params[0]; + + for (const auto &[_, nc] : *NickCoreList) + { + if (source.nc != nc && nc->IsServicesOper()) + memoserv->Send(source.GetNick(), nc->display, text, true); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends all services staff a memo containing \037memo-text\037.")); + + return true; + } +}; + +class MSStaff final + : public Module +{ + CommandMSStaff commandmsstaff; + +public: + MSStaff(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandmsstaff(this) + { + if (!memoserv) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSStaff) |
