summaryrefslogtreecommitdiff
path: root/modules/memoserv
diff options
context:
space:
mode:
authorSadie Powell <sadie@witchery.services>2024-01-24 12:34:03 +0000
committerSadie Powell <sadie@witchery.services>2024-01-24 12:34:34 +0000
commit484160eb4ed560eeda97c94a42dd8e31431ab251 (patch)
tree03600af16b55fa2f268904d7a42a8b87f86c09f4 /modules/memoserv
parent7ac1fe58478d58e2480b6919c4abf3a82929169c (diff)
Shuffle modules around a bit.
Diffstat (limited to 'modules/memoserv')
-rw-r--r--modules/memoserv/memoserv.cpp237
-rw-r--r--modules/memoserv/ms_cancel.cpp103
-rw-r--r--modules/memoserv/ms_check.cpp89
-rw-r--r--modules/memoserv/ms_del.cpp162
-rw-r--r--modules/memoserv/ms_ignore.cpp134
-rw-r--r--modules/memoserv/ms_info.cpp235
-rw-r--r--modules/memoserv/ms_list.cpp167
-rw-r--r--modules/memoserv/ms_read.cpp227
-rw-r--r--modules/memoserv/ms_rsend.cpp106
-rw-r--r--modules/memoserv/ms_send.cpp90
-rw-r--r--modules/memoserv/ms_sendall.cpp70
-rw-r--r--modules/memoserv/ms_set.cpp317
-rw-r--r--modules/memoserv/ms_staff.cpp67
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> &params) 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> &params) 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> &params) 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> &params) 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> &params) 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> &params) 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> &params) 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.