From 6302f980fe6dad83aab7d2dc1046dadea5ffd939 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 14 Dec 2015 16:52:24 -0500 Subject: New source tree structure for modules. From commands/cs_access => chanserv/access etc. --- modules/memoserv/CMakeLists.txt | 1 + modules/memoserv/cancel.cpp | 91 ++++++++++ modules/memoserv/check.cpp | 80 +++++++++ modules/memoserv/del.cpp | 148 ++++++++++++++++ modules/memoserv/ignore.cpp | 147 +++++++++++++++ modules/memoserv/info.cpp | 203 +++++++++++++++++++++ modules/memoserv/list.cpp | 163 +++++++++++++++++ modules/memoserv/main/CMakeLists.txt | 1 + modules/memoserv/main/ignore.cpp | 27 +++ modules/memoserv/main/ignore.h | 15 ++ modules/memoserv/main/ignoretype.cpp | 10 ++ modules/memoserv/main/ignoretype.h | 10 ++ modules/memoserv/main/memo.cpp | 72 ++++++++ modules/memoserv/main/memo.h | 29 +++ modules/memoserv/main/memoinfo.cpp | 63 +++++++ modules/memoserv/main/memoinfo.h | 23 +++ modules/memoserv/main/memoinfotype.cpp | 10 ++ modules/memoserv/main/memoinfotype.h | 10 ++ modules/memoserv/main/memoserv.cpp | 298 +++++++++++++++++++++++++++++++ modules/memoserv/main/memotype.cpp | 14 ++ modules/memoserv/main/memotype.h | 13 ++ modules/memoserv/read.cpp | 203 +++++++++++++++++++++ modules/memoserv/rsend.cpp | 105 +++++++++++ modules/memoserv/send.cpp | 81 +++++++++ modules/memoserv/sendall.cpp | 60 +++++++ modules/memoserv/set.cpp | 314 +++++++++++++++++++++++++++++++++ modules/memoserv/staff.cpp | 57 ++++++ 27 files changed, 2248 insertions(+) create mode 100644 modules/memoserv/CMakeLists.txt create mode 100644 modules/memoserv/cancel.cpp create mode 100644 modules/memoserv/check.cpp create mode 100644 modules/memoserv/del.cpp create mode 100644 modules/memoserv/ignore.cpp create mode 100644 modules/memoserv/info.cpp create mode 100644 modules/memoserv/list.cpp create mode 100644 modules/memoserv/main/CMakeLists.txt create mode 100644 modules/memoserv/main/ignore.cpp create mode 100644 modules/memoserv/main/ignore.h create mode 100644 modules/memoserv/main/ignoretype.cpp create mode 100644 modules/memoserv/main/ignoretype.h create mode 100644 modules/memoserv/main/memo.cpp create mode 100644 modules/memoserv/main/memo.h create mode 100644 modules/memoserv/main/memoinfo.cpp create mode 100644 modules/memoserv/main/memoinfo.h create mode 100644 modules/memoserv/main/memoinfotype.cpp create mode 100644 modules/memoserv/main/memoinfotype.h create mode 100644 modules/memoserv/main/memoserv.cpp create mode 100644 modules/memoserv/main/memotype.cpp create mode 100644 modules/memoserv/main/memotype.h create mode 100644 modules/memoserv/read.cpp create mode 100644 modules/memoserv/rsend.cpp create mode 100644 modules/memoserv/send.cpp create mode 100644 modules/memoserv/sendall.cpp create mode 100644 modules/memoserv/set.cpp create mode 100644 modules/memoserv/staff.cpp (limited to 'modules/memoserv') diff --git a/modules/memoserv/CMakeLists.txt b/modules/memoserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/memoserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/memoserv/cancel.cpp b/modules/memoserv/cancel.cpp new file mode 100644 index 000000000..e3da82581 --- /dev/null +++ b/modules/memoserv/cancel.cpp @@ -0,0 +1,91 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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" +#include "modules/memoserv.h" + +class CommandMSCancel : public Command +{ + public: + CommandMSCancel(Module *creator) : Command(creator, "memoserv/cancel", 1, 1) + { + this->SetDesc(_("Cancel the last memo you sent")); + this->SetSyntax(_("{\037user\037 | \037channel\037}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &nname = params[0]; + + bool ischan, isregistered; + MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(name, ischan, isregistered, false); + + if (!isregistered) + { + if (ischan) + source.Reply(_("Channel \002{0}\002 isn't registered."), nname); + else + source.Reply(_("\002{0}\002 isn't registered."), nname); + } + else + { + ChanServ::Channel *ci = NULL; + NickServ::Nick *na = NULL; + if (ischan) + ci = ChanServ::Find(nname); + else + na = NickServ::FindNick(nname); + if (mi) + { + auto memos = mi->GetMemos(); + for (int i = memos.size() - 1; i >= 0; --i) + { + MemoServ::Memo *m = memos[i]; + if (m->GetUnread() && source.nc->GetDisplay().equals_ci(m->GetSender())) + { + if (MemoServ::Event::OnMemoDel) + MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ischan ? ci->GetName() : na->GetAccount()->GetDisplay(), mi, m); + mi->Del(i); + source.Reply(_("Your last memo to \002{0}\002 has been cancelled."), nname); + return; + } + } + } + + source.Reply(_("No memo to \002{0}\002 was cancelable."), nname); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Cancels the last memo you sent to \037user\037, provided it has not yet been read.")); + return true; + } +}; + +class MSCancel : 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/check.cpp b/modules/memoserv/check.cpp new file mode 100644 index 000000000..75a8ee3d0 --- /dev/null +++ b/modules/memoserv/check.cpp @@ -0,0 +1,80 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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 : public Command +{ + public: + CommandMSCheck(Module *creator) : Command(creator, "memoserv/check", 1, 1) + { + this->SetDesc(_("Checks if last memo to a user was read")); + this->SetSyntax(_("\037user\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &recipient = params[0]; + + bool found = false; + + NickServ::Nick *na = NickServ::FindNick(recipient); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), recipient); + return; + } + + MemoServ::MemoInfo *mi = na->GetAccount()->GetMemos(); + + if (mi) + /* find the last memo */ + for (unsigned i = mi->GetMemos().size(); i > 0; --i) + { + MemoServ::Memo *m = mi->GetMemo(i - 1); + NickServ::Nick *na2 = NickServ::FindNick(m->GetSender()); + + if (na2 != NULL && na2->GetAccount() == source.GetAccount()) + { + found = true; /* Yes, we've found the memo */ + + if (m->GetUnread()) + source.Reply(_("The last memo you sent to \002{0}\002 (sent on \002{1}\002) has not yet been read."), na->GetNick(), Anope::strftime(m->GetTime(), source.GetAccount())); + else + source.Reply(_("The last memo you sent to \002{0}\002 (sent on \002{1}\002) has been read."), na->GetNick(), Anope::strftime(m->GetTime(), source.GetAccount())); + break; + } + } + + if (!found) + source.Reply(_("\002{0}\002 doesn't have a memo from you."), na->GetNick()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Checks whether the last memo you sent to \037user\037 has been read or not.")); + return true; + } +}; + +class MSCheck : 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/del.cpp b/modules/memoserv/del.cpp new file mode 100644 index 000000000..79af11adc --- /dev/null +++ b/modules/memoserv/del.cpp @@ -0,0 +1,148 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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" +#include "modules/memoserv.h" + +class CommandMSDel : 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 ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + MemoServ::MemoInfo *mi; + ChanServ::Channel *ci = NULL; + Anope::string numstr = !params.empty() ? params[0] : "", chan; + + if (!numstr.empty() && numstr[0] == '#') + { + chan = numstr; + numstr = params.size() > 1 ? params[1] : ""; + + ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + mi = ci->GetMemos(); + } + else + mi = source.nc->GetMemos(); + + if (numstr.empty() || (!isdigit(numstr[0]) && !numstr.equals_ci("ALL") && !numstr.equals_ci("LAST"))) + { + this->OnSyntaxError(source, numstr); + return; + } + + if (!mi || mi->GetMemos().empty()) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no memos."), chan); + else + source.Reply(_("You have no memos.")); + return; + } + + auto memos = mi->GetMemos(); + + if (isdigit(numstr[0])) + { + NumberList(numstr, true, + [&](unsigned int number) + { + if (!number || number > memos.size()) + return; + + if (MemoServ::Event::OnMemoDel) + MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, mi->GetMemo(number - 1)); + + mi->Del(number - 1); + source.Reply(_("Memo \002{0}\002 has been deleted."), number); + }, + [](){}); + } + else if (numstr.equals_ci("LAST")) + { + /* Delete last memo. */ + if (MemoServ::Event::OnMemoDel) + MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, mi->GetMemo(memos.size() - 1)); + mi->Del(memos.size() - 1); + source.Reply(_("Memo \002{0}\002 has been deleted."), memos.size() + 1); + } + else + { + /* Delete all memos. */ + std::for_each(memos.begin(), memos.end(), + [&](MemoServ::Memo *m) + { + if (MemoServ::Event::OnMemoDel) + MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, m); + delete m; + }); + if (!chan.empty()) + source.Reply(_("All memos for channel \002{0}\002 have been deleted."), chan); + else + source.Reply(_("All of your memos have been deleted.")); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Deletes the specified memo or memos." + " You can supply multiple memo numbers or ranges of numbers instead of a single number, as in the second example below." + " If \002LAST\002 is given, the last memo will be deleted." + " If \002ALL\002 is given, deletes all of your memos.\n" + "\n" + "Examples:\n" + "\n" + " {0} 1\n" + " Deletes your first memo.\n" + "\n" + " {0} 2-5,7-9\n" + " Deletes memos numbered 2 through 5 and 7 through 9."), + source.command); + return true; + } +}; + +class MSDel : 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/ignore.cpp b/modules/memoserv/ignore.cpp new file mode 100644 index 000000000..c454444ea --- /dev/null +++ b/modules/memoserv/ignore.cpp @@ -0,0 +1,147 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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 : 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 ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if (!MemoServ::service) + 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, isregistered; + MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(channel, ischan, isregistered, true); + ChanServ::Channel *ci = ChanServ::Find(channel); + if (!isregistered) + { + if (ischan) + source.Reply(_("Channel \002{0}\002 isn't registered."), channel); + else + source.Reply(_("\002{0}\002 isn't registered."), channel); + return; + } + + if (ischan && !source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + auto ignores = mi->GetIgnores(); + + if (command.equals_ci("ADD") && !param.empty()) + { + if (ignores.size() >= Config->GetModule(this->owner)->Get("max", "32")) + { + source.Reply(_("Sorry, the memo ignore list for \002{0}\002 is full."), channel); + return; + } + + for (MemoServ::Ignore *ign : ignores) + if (ign->GetMask().equals_ci(param)) + { + source.Reply(_("\002{0}\002 is already on your memo ignore list."), param); + return; + } + + MemoServ::Ignore *ign = MemoServ::service->CreateIgnore(); + ign->SetMemoInfo(mi); + ign->SetMask(param); + + source.Reply(_("\002{0}\002 has been added to your memo ignore list."), param); + } + else if (command.equals_ci("DEL") && !param.empty()) + { + for (MemoServ::Ignore *ign : ignores) + if (ign->GetMask().equals_ci(param)) + { + delete ign; + source.Reply(_("\002{0}\002 has been removed from your memo ignore list."), param); + return; + } + + source.Reply(_("\002{0}\002 is not on your memo ignore list."), param); + } + else if (command.equals_ci("LIST")) + { + if (ignores.empty()) + { + source.Reply(_("Your memo ignore list is empty.")); + return; + } + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Mask")); + for (MemoServ::Ignore *ign : ignores) + { + ListFormatter::ListEntry entry; + entry["Mask"] = ign->GetMask(); + list.AddEntry(entry); + } + + source.Reply(_("Memo ignore list:")); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to ignore users from memoing you or a channel." + " If someone on the memo ignore list tries to memo you or a channel, they will not be told that you have them ignored.")); + return true; + } +}; + +class MSIgnore : 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/info.cpp b/modules/memoserv/info.cpp new file mode 100644 index 000000000..d9bee1cb5 --- /dev/null +++ b/modules/memoserv/info.cpp @@ -0,0 +1,203 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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 : public Command +{ + public: + CommandMSInfo(Module *creator) : Command(creator, "memoserv/info", 0, 1) + { + this->SetDesc(_("Displays information about your memos")); + this->SetSyntax(_("[\037user\037 | \037channel\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + NickServ::Account *nc = source.nc; + MemoServ::MemoInfo *mi; + NickServ::Nick *na = NULL; + ChanServ::Channel *ci = NULL; + const Anope::string &nname = !params.empty() ? params[0] : ""; + bool hardmax; + + if (!nname.empty() && nname[0] != '#' && source.HasPriv("memoserv/info")) + { + na = NickServ::FindNick(nname); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nname); + return; + } + mi = na->GetAccount()->GetMemos(); + hardmax = na->GetAccount()->HasFieldS("MEMO_HARDMAX"); + } + else if (!nname.empty() && nname[0] == '#') + { + ci = ChanServ::Find(nname); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), nname); + return; + } + + if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + mi = ci->GetMemos(); + hardmax = ci->HasFieldS("MEMO_HARDMAX"); + } + else if (!nname.empty()) /* It's not a chan and we aren't an oper */ + { + source.Reply(_("Access denied. You do not have the correct operator privilege to see the memo info of \002{0}\002."), nname); + return; + } + else + { + mi = nc->GetMemos(); + hardmax = nc->HasFieldS("MEMO_HARDMAX"); + } + if (!mi) + return; + + auto memos = mi->GetMemos(); + + if (!nname.empty() && (ci || na->GetAccount() != nc)) + { + if (memos.empty()) + source.Reply(_("%s currently has no memos."), nname.c_str()); + else if (memos.size() == 1) + { + if (mi->GetMemo(0)->GetUnread()) + source.Reply(_("\002{0}\002 currently has \0021\002 memo, and it has not yet been read."), nname); + else + source.Reply(_("\002{0}\002 currently has \0021\002 memo."), nname); + } + else + { + unsigned count = 0, i, end; + for (i = 0, end = memos.size(); i < end; ++i) + if (mi->GetMemo(i)->GetUnread()) + ++count; + if (count == memos.size()) + source.Reply(_("\002{0}\002 currently has \002{1]\002 memos; all of them are unread."), nname, count); + else if (!count) + source.Reply(_("\002{0}\002 currently has \002{1}\002 memos."), nname, memos.size()); + else if (count == 1) + source.Reply(_("\002{0}\002 currently has \002{1}\002 memos, of which \0021\002 is unread."), nname, memos.size()); + else + source.Reply(_("\002{0}\002 currently has \002{1]\002 memos, of which \002{2}\002 are unread."), nname, memos.size(), count); + } + if (mi->GetMemoMax() >= 0) + { + if (hardmax) + source.Reply(_("The memo limit of \002{0}\002 is \002{1}\002, and may not be changed."), nname, mi->GetMemoMax()); + else + source.Reply(_("The memo limit of \002{0}\002 is \002{1}\002."), nname, mi->GetMemoMax()); + } + else + source.Reply(_("\002{0}\002 has no memo limit."), nname); + + if (na) + { + if (na->GetAccount()->HasFieldS("MEMO_RECEIVE") && na->GetAccount()->HasFieldS("MEMO_SIGNON")) + source.Reply(_("\002{0}\002 is notified of new memos at logon and when they arrive."), nname); + else if (na->GetAccount()->HasFieldS("MEMO_RECEIVE")) + source.Reply(_("\002{0}\002 is notified when new memos arrive."), nname); + else if (na->GetAccount()->HasFieldS("MEMO_SIGNON")) + source.Reply(_("\002{0}\002 is notified of news memos at logon."), nname); + else + source.Reply(_("\002{0}\002 is not notified of new memos."), nname); + } + } + else + { + if (memos.empty()) + source.Reply(_("You currently have no memos.")); + else if (memos.size() == 1) + { + if (mi->GetMemo(0)->GetUnread()) + 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 + { + unsigned count = 0, i, end; + for (i = 0, end = memos.size(); i < end; ++i) + if (mi->GetMemo(i)->GetUnread()) + ++count; + if (count == memos.size()) + source.Reply(_("You currently have \002{0}\002 memos; all of them are unread."), count); + else if (!count) + source.Reply(_("You currently have \002{0}\002 memos."), memos.size()); + else if (count == 1) + source.Reply(_("You currently have \002{0}\002 memos, of which \0021\002 is unread."), memos.size()); + else + source.Reply(_("You currently have \002{0}\002 memos, of which \002{1}\002 are unread."), memos.size(), count); + } + + if (!mi->GetMemoMax()) + { + 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->GetMemoMax() > 0) + { + if (!source.IsServicesOper() && hardmax) + source.Reply(_("Your memo limit is \002{0}\002, and may not be changed."), mi->GetMemoMax()); + else + source.Reply(_("Your memo limit is \002{0}\002."), mi->GetMemoMax()); + } + else + source.Reply(_("You have no limit on the number of memos you may keep.")); + + if (nc->HasFieldS("MEMO_RECEIVE") && nc->HasFieldS("MEMO_SIGNON")) + source.Reply(_("You will be notified of new memos at logon and when they arrive.")); + else if (nc->HasFieldS("MEMO_RECEIVE")) + source.Reply(_("You will be notified when new memos arrive.")); + else if (nc->HasFieldS("MEMO_SIGNON")) + 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 + { + source.Reply(_("Without a parameter, displays information on the number of memos you have, how many of them are unread, and how many total memos you can receive." + " With a parameter, displays the same information for the given \037user\037 or \037channel\037, if you have the appropriate privilege.\n" + "\n" + "Use of this command on a channel requires the \002{0}\002 privilege on \037channel\037."), + source.command); + + return true; + } +}; + +class MSInfo : 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/list.cpp b/modules/memoserv/list.cpp new file mode 100644 index 000000000..8215315b0 --- /dev/null +++ b/modules/memoserv/list.cpp @@ -0,0 +1,163 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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 : 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 ¶ms) override + { + + Anope::string param = !params.empty() ? params[0] : "", chan; + ChanServ::Channel *ci = NULL; + MemoServ::MemoInfo *mi; + + if (!param.empty() && param[0] == '#') + { + chan = param; + param = params.size() > 1 ? params[1] : ""; + + ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + mi = ci->GetMemos(); + } + else + mi = source.nc->GetMemos(); + + if (!param.empty() && !isdigit(param[0]) && !param.equals_ci("NEW")) + { + this->OnSyntaxError(source, param); + return; + } + + if (!mi) + return; + + auto memos = mi->GetMemos(); + + if (!memos.size()) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no memos."), chan); + else + source.Reply(_("You have no memos.")); + return; + } + + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Number")).AddColumn(_("Sender")).AddColumn(_("Date/Time")); + + if (!param.empty() && isdigit(param[0])) + { + NumberList(param, false, + [&](unsigned int number) + { + if (!number || number > memos.size()) + return; + + MemoServ::Memo *m = mi->GetMemo(number - 1); + + ListFormatter::ListEntry entry; + entry["Number"] = (m->GetUnread() ? "* " : " ") + stringify(number); + entry["Sender"] = m->GetSender(); + entry["Date/Time"] = Anope::strftime(m->GetTime(), source.GetAccount()); + list.AddEntry(entry); + }, + []{}); + } + else + { + if (!param.empty()) + { + unsigned i, end; + for (i = 0, end = memos.size(); i < end; ++i) + if (mi->GetMemo(i)->GetUnread()) + break; + if (i == end) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no new memos."), chan); + else + source.Reply(_("You have no new memos.")); + return; + } + } + + for (unsigned i = 0, end = memos.size(); i < end; ++i) + { + if (!param.empty() && !mi->GetMemo(i)->GetUnread()) + continue; + + MemoServ::Memo *m = mi->GetMemo(i); + + ListFormatter::ListEntry entry; + entry["Number"] = (m->GetUnread() ? "* " : " ") + stringify(i + 1); + entry["Sender"] = m->GetSender(); + entry["Date/Time"] = Anope::strftime(m->GetTime(), source.GetAccount()); + list.AddEntry(entry); + } + } + + std::vector replies; + list.Process(replies); + + source.Reply(_("Memos for \002{0}\002:"), ci ? ci->GetName().c_str() : source.GetNick().c_str()); + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists the memos you currently have." + " With \002NEW\002, lists only new (unread) memos." + " Unread memos are marked with a \"*\" to the left of the memo number." + " You can also specify a list of numbers, as in the example below:\n" + "\n" + "Example:\n" + "\n" + " {0} 2-5,7-9\n" + " Lists memos numbered 2 through 5 and 7 through 9.")); + return true; + } +}; + +class MSList : 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/main/CMakeLists.txt b/modules/memoserv/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/memoserv/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/memoserv/main/ignore.cpp b/modules/memoserv/main/ignore.cpp new file mode 100644 index 000000000..e85011c00 --- /dev/null +++ b/modules/memoserv/main/ignore.cpp @@ -0,0 +1,27 @@ +#include "memo.h" +#include "ignoretype.h" + +IgnoreImpl::~IgnoreImpl() +{ +} + +MemoServ::MemoInfo *IgnoreImpl::GetMemoInfo() +{ + return Get(&IgnoreType::mi); +} + +void IgnoreImpl::SetMemoInfo(MemoServ::MemoInfo *m) +{ + Set(&IgnoreType::mi, m); +} + +Anope::string IgnoreImpl::GetMask() +{ + return Get(&IgnoreType::mask); +} + +void IgnoreImpl::SetMask(const Anope::string &mask) +{ + Set(&IgnoreType::mask, mask); +} + diff --git a/modules/memoserv/main/ignore.h b/modules/memoserv/main/ignore.h new file mode 100644 index 000000000..c98b44adf --- /dev/null +++ b/modules/memoserv/main/ignore.h @@ -0,0 +1,15 @@ +#include "modules/memoserv.h" + +class IgnoreImpl : public MemoServ::Ignore +{ + public: + IgnoreImpl(Serialize::TypeBase *type) : MemoServ::Ignore(type) { } + IgnoreImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::Ignore(type, id) { } + ~IgnoreImpl(); + + MemoServ::MemoInfo *GetMemoInfo() override; + void SetMemoInfo(MemoServ::MemoInfo *) override; + + Anope::string GetMask() override; + void SetMask(const Anope::string &mask) override; +}; diff --git a/modules/memoserv/main/ignoretype.cpp b/modules/memoserv/main/ignoretype.cpp new file mode 100644 index 000000000..375f83272 --- /dev/null +++ b/modules/memoserv/main/ignoretype.cpp @@ -0,0 +1,10 @@ +#include "module.h" +#include "ignoretype.h" + +IgnoreType::IgnoreType(Module *me) : Serialize::Type(me, "MemoIgnore") + , mi(this, "mi", true) + , mask(this, "mask") +{ + +} + diff --git a/modules/memoserv/main/ignoretype.h b/modules/memoserv/main/ignoretype.h new file mode 100644 index 000000000..5c656f9b8 --- /dev/null +++ b/modules/memoserv/main/ignoretype.h @@ -0,0 +1,10 @@ +#include "ignore.h" + +class IgnoreType : public Serialize::Type +{ + public: + Serialize::ObjectField mi; + Serialize::Field mask; + + IgnoreType(Module *); +}; diff --git a/modules/memoserv/main/memo.cpp b/modules/memoserv/main/memo.cpp new file mode 100644 index 000000000..bfae64284 --- /dev/null +++ b/modules/memoserv/main/memo.cpp @@ -0,0 +1,72 @@ +//#include "memo.h" +#include "memotype.h" + +/*MemoImpl::MemoImpl() +{ + //unread = receipt = false; +}*/ + +MemoImpl::~MemoImpl() +{ +} + +MemoServ::MemoInfo *MemoImpl::GetMemoInfo() +{ + return Get(&MemoType::mi); +} + +void MemoImpl::SetMemoInfo(MemoServ::MemoInfo *mi) +{ + Set(&MemoType::mi, mi); +} + +time_t MemoImpl::GetTime() +{ + return Get(&MemoType::time); +} + +void MemoImpl::SetTime(const time_t &t) +{ + Set(&MemoType::time, t); +} + +Anope::string MemoImpl::GetSender() +{ + return Get(&MemoType::sender); +} + +void MemoImpl::SetSender(const Anope::string &s) +{ + Set(&MemoType::sender, s); +} + +Anope::string MemoImpl::GetText() +{ + return Get(&MemoType::text); +} + +void MemoImpl::SetText(const Anope::string &t) +{ + Set(&MemoType::text, t); +} + +bool MemoImpl::GetUnread() +{ + return Get(&MemoType::unread); +} + +void MemoImpl::SetUnread(const bool &b) +{ + Set(&MemoType::unread, b); +} + +bool MemoImpl::GetReceipt() +{ + return Get(&MemoType::receipt); +} + +void MemoImpl::SetReceipt(const bool &b) +{ + Set(&MemoType::receipt, b); +} + diff --git a/modules/memoserv/main/memo.h b/modules/memoserv/main/memo.h new file mode 100644 index 000000000..1a230c1e9 --- /dev/null +++ b/modules/memoserv/main/memo.h @@ -0,0 +1,29 @@ +#include "modules/memoserv.h" + +class MemoImpl : public MemoServ::Memo +{ + public: + MemoImpl(Serialize::TypeBase *type) : MemoServ::Memo(type) { } + MemoImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::Memo(type, id) { } + //using MemoServ::Memo::Memo; +// MemoImpl(); + ~MemoImpl(); + + MemoServ::MemoInfo *GetMemoInfo() override; + void SetMemoInfo(MemoServ::MemoInfo *) override; + + time_t GetTime() override; + void SetTime(const time_t &) override; + + Anope::string GetSender() override; + void SetSender(const Anope::string &) override; + + Anope::string GetText() override; + void SetText(const Anope::string &) override; + + bool GetUnread() override; + void SetUnread(const bool &) override; + + bool GetReceipt() override; + void SetReceipt(const bool &) override; +}; diff --git a/modules/memoserv/main/memoinfo.cpp b/modules/memoserv/main/memoinfo.cpp new file mode 100644 index 000000000..45d95533e --- /dev/null +++ b/modules/memoserv/main/memoinfo.cpp @@ -0,0 +1,63 @@ +#include "memoinfotype.h" + +MemoServ::Memo *MemoInfoImpl::GetMemo(unsigned index) +{ + auto memos = GetMemos(); + return index >= memos.size() ? nullptr : memos[index]; +} + +unsigned MemoInfoImpl::GetIndex(MemoServ::Memo *m) +{ + auto memos = GetMemos(); + for (unsigned i = 0; i < memos.size(); ++i) + if (this->GetMemo(i) == m) + return i; + return -1; // XXX wtf? +} + +void MemoInfoImpl::Del(unsigned index) +{ + delete GetMemo(index); +} + +bool MemoInfoImpl::HasIgnore(User *u) +{ + for (MemoServ::Ignore *ign : GetIgnores()) + { + const Anope::string &mask = ign->GetMask(); + if (u->nick.equals_ci(mask) || (u->Account() && u->Account()->GetDisplay().equals_ci(mask)) || Anope::Match(u->GetMask(), mask)) + return true; + } + return false; +} + +Serialize::Object *MemoInfoImpl::GetOwner() +{ + return Get(&MemoInfoType::owner); +} + +void MemoInfoImpl::SetOwner(Serialize::Object *o) +{ + Set(&MemoInfoType::owner, o); +} + +int16_t MemoInfoImpl::GetMemoMax() +{ + return Get(&MemoInfoType::memomax); +} + +void MemoInfoImpl::SetMemoMax(const int16_t &i) +{ + Set(&MemoInfoType::memomax, i); +} + +std::vector MemoInfoImpl::GetMemos() +{ + return GetRefs(MemoServ::memo); +} + +std::vector MemoInfoImpl::GetIgnores() +{ + return GetRefs(MemoServ::ignore); +} + diff --git a/modules/memoserv/main/memoinfo.h b/modules/memoserv/main/memoinfo.h new file mode 100644 index 000000000..22ea0734f --- /dev/null +++ b/modules/memoserv/main/memoinfo.h @@ -0,0 +1,23 @@ +#include "modules/memoserv.h" + +class MemoInfoImpl : public MemoServ::MemoInfo +{ + public: + MemoInfoImpl(Serialize::TypeBase *type) : MemoServ::MemoInfo(type) { } + MemoInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::MemoInfo(type, id) { } + + MemoServ::Memo *GetMemo(unsigned index) override; + unsigned GetIndex(MemoServ::Memo *m) override; + void Del(unsigned index) override; + bool HasIgnore(User *u) override; + + Serialize::Object *GetOwner() override; + void SetOwner(Serialize::Object *) override; + + int16_t GetMemoMax() override; + void SetMemoMax(const int16_t &) override; + + std::vector GetMemos() override; + std::vector GetIgnores() override; +}; + diff --git a/modules/memoserv/main/memoinfotype.cpp b/modules/memoserv/main/memoinfotype.cpp new file mode 100644 index 000000000..7eb3af72c --- /dev/null +++ b/modules/memoserv/main/memoinfotype.cpp @@ -0,0 +1,10 @@ +#include "module.h" +#include "memoinfotype.h" + +MemoInfoType::MemoInfoType(Module *me) : Serialize::Type(me, "MemoInfo") + , owner(this, "owner", true) + , memomax(this, "memomax") +{ + +} + diff --git a/modules/memoserv/main/memoinfotype.h b/modules/memoserv/main/memoinfotype.h new file mode 100644 index 000000000..724a898b5 --- /dev/null +++ b/modules/memoserv/main/memoinfotype.h @@ -0,0 +1,10 @@ +#include "memoinfo.h" + +class MemoInfoType : public Serialize::Type +{ + public: + Serialize::ObjectField owner; + Serialize::Field memomax; + + MemoInfoType(Module *); +}; diff --git a/modules/memoserv/main/memoserv.cpp b/modules/memoserv/main/memoserv.cpp new file mode 100644 index 000000000..a52ee746c --- /dev/null +++ b/modules/memoserv/main/memoserv.cpp @@ -0,0 +1,298 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_update.h" +#include "modules/help.h" +#include "modules/bs_bot.h" +#include "modules/memoserv.h" +//#include "memoinfo.h" +//#include "memo.h" +//#include "ignore.h" +#include "memotype.h" +#include "memoinfotype.h" +#include "ignoretype.h" + +class MemoServCore : public Module, public MemoServ::MemoServService + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Reference MemoServ; + EventHandlers onmemosend; + EventHandlers onmemodel; + + MemoInfoType memoinfo_type; + MemoType memo_type; + IgnoreType ignore_type; + + bool SendMemoMail(NickServ::Account *nc, MemoServ::MemoInfo *mi, MemoServ::Memo *m) + { + Anope::string subject = Language::Translate(nc, Config->GetBlock("mail")->Get("memo_subject").c_str()), + message = Language::Translate(Config->GetBlock("mail")->Get("memo_message").c_str()); + + subject = subject.replace_all_cs("%n", nc->GetDisplay()); + subject = subject.replace_all_cs("%s", m->GetSender()); + subject = subject.replace_all_cs("%d", stringify(mi->GetIndex(m) + 1)); + subject = subject.replace_all_cs("%t", m->GetText()); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + + message = message.replace_all_cs("%n", nc->GetDisplay()); + message = message.replace_all_cs("%s", m->GetSender()); + message = message.replace_all_cs("%d", stringify(mi->GetIndex(m) + 1)); + message = message.replace_all_cs("%t", m->GetText()); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + + return Mail::Send(nc, subject, message); + } + + public: + MemoServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , MemoServ::MemoServService(this) + , onmemosend(this) + , onmemodel(this) + , memoinfo_type(this) + , memo_type(this) + , ignore_type(this) + { + } + + MemoResult Send(const Anope::string &source, const Anope::string &target, const Anope::string &message, bool force) override + { + bool ischan, isregistered; + MemoServ::MemoInfo *mi = GetMemoInfo(target, ischan, isregistered, true); + + if (mi == NULL) + return MEMO_INVALID_TARGET; + + User *sender = User::Find(source); + if (sender != NULL && !sender->HasPriv("memoserv/no-limit") && !force) + { + time_t send_delay = Config->GetModule("memoserv")->Get("senddelay"); + if (send_delay > 0 && sender->lastmemosend + send_delay > Anope::CurTime) + return MEMO_TOO_FAST; + else if (!mi->GetMemoMax()) + return MEMO_TARGET_FULL; + else if (mi->GetMemoMax() > 0 && mi->GetMemos().size() >= static_cast(mi->GetMemoMax())) + return MEMO_TARGET_FULL; + else if (mi->HasIgnore(sender)) + return MEMO_SUCCESS; + } + + if (sender != NULL) + sender->lastmemosend = Anope::CurTime; + + MemoServ::Memo *m = new MemoImpl(&memo_type); + m->SetMemoInfo(mi); + m->SetSender(source); + m->SetTime(Anope::CurTime); + m->SetText(message); + m->SetUnread(true); + + this->onmemosend(&MemoServ::Event::MemoSend::OnMemoSend, source, target, mi, m); + + if (ischan) + { + ChanServ::Channel *ci = ChanServ::Find(target); + + if (ci->c) + { + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + ChanUserContainer *cu = it->second; + + if (ci->AccessFor(cu->user).HasPriv("MEMO")) + { + if (cu->user->Account() && cu->user->Account()->HasFieldS("MEMO_RECEIVE")) + cu->user->SendMessage(*MemoServ, _("There is a new memo on channel \002{0}\002. Type \002{1}{2} READ {3} {4}\002 to read it."), ci->GetName(), Config->StrictPrivmsg, MemoServ->nick, ci->GetName(), mi->GetMemos().size()); // XXX + } + } + } + } + else + { + NickServ::Account *nc = NickServ::FindNick(target)->GetAccount(); + + if (nc->HasFieldS("MEMO_RECEIVE")) + for (User *u : nc->users) + u->SendMessage(*MemoServ, _("You have a new memo from \002{0}\002. Type \002{1}{2} READ {3}\002 to read it."), source, Config->StrictPrivmsg, MemoServ->nick, mi->GetMemos().size());//XXX + + /* let's get out the mail if set in the nickcore - certus */ + if (nc->HasFieldS("MEMO_MAIL")) + SendMemoMail(nc, mi, m); + } + + return MEMO_SUCCESS; + } + + void Check(User *u) override + { + NickServ::Account *nc = u->Account(); + if (!nc || !nc->GetMemos()) + return; + + auto memos = nc->GetMemos()->GetMemos(); + unsigned i = 0, end = memos.size(), newcnt = 0; + for (; i < end; ++i) + if (memos[i]->GetUnread()) + ++newcnt; + if (newcnt > 0) + u->SendMessage(*MemoServ, newcnt == 1 ? _("You have 1 new memo.") : _("You have %d new memos."), newcnt); + if (nc->GetMemos()->GetMemoMax() > 0 && memos.size() >= static_cast(nc->GetMemos()->GetMemoMax())) + { + if (memos.size() > static_cast(nc->GetMemos()->GetMemoMax())) + 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->GetMemos()->GetMemoMax()); + 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->GetMemos()->GetMemoMax()); + } + } + + MemoServ::Memo *CreateMemo() override + { + return new MemoImpl(&memo_type); + } + + MemoServ::Ignore *CreateIgnore() override + { + return new IgnoreImpl(&ignore_type); + } + + MemoServ::MemoInfo *GetMemoInfo(const Anope::string &target, bool &is_registered, bool &ischan, bool create) override + { + if (!target.empty() && target[0] == '#') + { + ischan = true; + ChanServ::Channel *ci = ChanServ::Find(target); + if (ci != NULL) + { + is_registered = true; + if (create && !ci->GetMemos()) + { + MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); + mi->SetOwner(ci); + } + return ci->GetMemos(); + } + else + is_registered = false; + } + else + { + ischan = false; + NickServ::Nick *na = NickServ::FindNick(target); + if (na != NULL) + { + is_registered = true; + if (create && !na->GetAccount()->GetMemos()) + { + MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); + mi->SetOwner(na->GetAccount()); + } + return na->GetAccount()->GetMemos(); + } + else + is_registered = false; + } + + return NULL; + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &msnick = conf->GetModule(this)->Get("client"); + + if (msnick.empty()) + throw ConfigException(Module::name + ": must be defined"); + + ServiceBot *bi = ServiceBot::Find(msnick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + msnick); + + MemoServ = bi; + } + + void OnNickCoreCreate(NickServ::Account *nc) override + { + MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); + mi->SetOwner(mi); + mi->SetMemoMax(Config->GetModule(this)->Get("maxmemos")); + } + + void OnCreateChan(ChanServ::Channel *ci) override + { + MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); + mi->SetOwner(ci); + mi->SetMemoMax(Config->GetModule(this)->Get("maxmemos")); + } + + void OnBotDelete(ServiceBot *bi) override + { + if (bi == MemoServ) + MemoServ = NULL; + } + + void OnNickIdentify(User *u) override + { + this->Check(u); + } + + void OnJoinChannel(User *u, Channel *c) override + { + if (u->server && u->server->IsSynced() && c->ci && c->ci->GetMemos() && !c->ci->GetMemos()->GetMemos().empty() && c->ci->AccessFor(u).HasPriv("MEMO")) + { + if (c->ci->GetMemos()->GetMemos().size() == 1) + u->SendMessage(*MemoServ, _("There is \002{0}\002 memo on channel \002{1}\002."), c->ci->GetMemos()->GetMemos().size(), c->ci->GetName()); + else + u->SendMessage(*MemoServ, _("There are \002{0}\002 memos on channel \002{1}\002."), c->ci->GetMemos()->GetMemos().size(), c->ci->GetName()); + } + } + + void OnUserAway(User *u, const Anope::string &message) override + { + if (message.empty()) + this->Check(u); + } + + void OnNickUpdate(User *u) override + { + this->Check(u); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector ¶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 ¶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/main/memotype.cpp b/modules/memoserv/main/memotype.cpp new file mode 100644 index 000000000..c9144bbaa --- /dev/null +++ b/modules/memoserv/main/memotype.cpp @@ -0,0 +1,14 @@ +#include "module.h" +#include "memotype.h" + +MemoType::MemoType(Module *me) : Serialize::Type(me, "Memo") + , mi(this, "mi", true) + , time(this, "time") + , sender(this, "sender") + , text(this, "text") + , unread(this, "unread") + , receipt(this, "receipt") +{ + +} + diff --git a/modules/memoserv/main/memotype.h b/modules/memoserv/main/memotype.h new file mode 100644 index 000000000..16ac6f082 --- /dev/null +++ b/modules/memoserv/main/memotype.h @@ -0,0 +1,13 @@ +#include "memo.h" + +class MemoType : public Serialize::Type +{ + public: + Serialize::ObjectField mi; + Serialize::Field time; + Serialize::Field sender; + Serialize::Field text; + Serialize::Field unread, receipt; + + MemoType(Module *); +}; diff --git a/modules/memoserv/read.cpp b/modules/memoserv/read.cpp new file mode 100644 index 000000000..687843124 --- /dev/null +++ b/modules/memoserv/read.cpp @@ -0,0 +1,203 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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" +#include "modules/memoserv.h" + +static void rsend_notify(CommandSource &source, MemoServ::MemoInfo *mi, MemoServ::Memo *m, const Anope::string &targ) +{ + /* Only send receipt if memos are allowed */ + if (MemoServ::service && !Anope::ReadOnly) + { + /* Get nick alias for sender */ + NickServ::Nick *na = NickServ::FindNick(m->GetSender()); + + if (!na) + return; + + /* Get nick core for sender */ + NickServ::Account *nc = na->GetAccount(); + + if (!nc) + return; + + /* Text of the memo varies if the recipient was a + nick or channel */ + Anope::string text = Anope::printf(Language::Translate(na->GetAccount(), _("\002[auto-memo]\002 The memo you sent to \002%s\002 has been viewed.")), targ.c_str()); + + /* Send notification */ + MemoServ::service->Send(source.GetNick(), m->GetSender(), 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 \002{0}\002 informing him/her you have read his/her memo."), nc->GetDisplay()); + } + + /* Remove receipt flag from the original memo */ + m->SetReceipt(false); +} + +class CommandMSRead : public Command +{ + static void DoRead(CommandSource &source, MemoServ::MemoInfo *mi, ChanServ::Channel *ci, unsigned index) + { + MemoServ::Memo *m = mi->GetMemo(index); + if (!m) + return; + + if (ci) + source.Reply(_("Memo \002{0}\002 from \002{1}\002 (\002{2}\002)."), index + 1, m->GetSender(), Anope::strftime(m->GetTime(), source.GetAccount())); + else + source.Reply(_("Memo \002{0}\002 from \002{1}\002 (\002{2}\002)."), index + 1, m->GetSender(), Anope::strftime(m->GetTime(), source.GetAccount())); + + ServiceBot *bi; + Anope::string cmd; + if (Command::FindCommandFromService("memoserv/del", bi, cmd)) + { + if (ci) + source.Reply(_("To delete, use \002{0}{1} {2} {3} {4}\002"), Config->StrictPrivmsg, bi->nick, cmd, ci->GetName(), index + 1); + else + source.Reply(_("To delete, use \002{0}{1} {2} {3}\002"), Config->StrictPrivmsg, bi->nick, cmd, index + 1); + } + + source.Reply(m->GetText()); + m->SetUnread(false); + + /* Check if a receipt notification was requested */ + if (m->GetReceipt()) + rsend_notify(source, mi, m, ci ? ci->GetName() : source.GetNick()); + } + + 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}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + + MemoServ::MemoInfo *mi; + ChanServ::Channel *ci = NULL; + Anope::string numstr = params[0], chan; + + if (!numstr.empty() && numstr[0] == '#') + { + chan = numstr; + numstr = params.size() > 1 ? params[1] : ""; + + ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + mi = ci->GetMemos(); + } + else + mi = source.nc->GetMemos(); + + if (numstr.empty() || (!numstr.equals_ci("LAST") && !numstr.equals_ci("NEW") && !numstr.is_number_only())) + { + this->OnSyntaxError(source, numstr); + return; + } + + if (!mi) + return; + + auto memos = mi->GetMemos(); + + if (memos.empty()) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no memos."), chan); + else + source.Reply(_("You have no memos.")); + return; + } + + int i, end; + + if (numstr.equals_ci("NEW")) + { + int readcount = 0; + for (i = 0, end = memos.size(); i < end; ++i) + if (mi->GetMemo(i)->GetUnread()) + { + DoRead(source, mi, ci, i); + ++readcount; + } + if (!readcount) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no new memos."), chan); + else + source.Reply(_("You have no new memos.")); + } + } + else if (numstr.equals_ci("LAST")) + { + for (i = 0, end = memos.size() - 1; i < end; ++i); + DoRead(source, mi, ci, i); + } + else /* number[s] */ + { + NumberList(numstr, false, + [&](unsigned int number) + { + if (!number || number > memos.size()) + return; + + DoRead(source, mi, ci, number - 1); + }, + []{}); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends you the text of the memos specified." + " If LAST is given, sends you the memo you most recently received." + " If NEW is given, sends you all of your new memos." + " Otherwise, sends you memo number \037num\037." + " You can also give a list of numbers, as in the example:\n" + "\n" + "Example:\n" + "\n" + " {0} 2-5,7-9\n" + " Displays memos numbered 2 through 5 and 7 through 9."), + source.command); + return true; + } +}; + +class MSRead : 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/rsend.cpp b/modules/memoserv/rsend.cpp new file mode 100644 index 000000000..16f6105fa --- /dev/null +++ b/modules/memoserv/rsend.cpp @@ -0,0 +1,105 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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" +#include "modules/memoserv.h" + +class CommandMSRSend : 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 ¶ms) override + { +#if 0 + if (!MemoServ::service) + return; + + if (Anope::ReadOnly && !source.IsOper()) + { + source.Reply(_("Sorry, memo sending is temporarily disabled.")); + return; + } + + const Anope::string &nick = params[0]; + const Anope::string &text = params[1]; + const NickServ::Nick *na = NULL; + + /* prevent user from rsend to themselves */ + if ((na = NickServ::FindNick(nick)) && na->GetAccount() == source.GetAccount()) + { + source.Reply(_("You can not request a receipt when sending a memo to yourself.")); + return; + } + + if (Config->GetModule(this->owner)->Get("operonly") && !source.IsServicesOper()) + source.Reply(_("Access denied. This command is for operators only.")); + else + { + MemoServ::MemoServService::MemoResult result = MemoServ::service->Send(source.GetNick(), nick, text); + if (result == MemoServ::MemoServService::MEMO_INVALID_TARGET) + source.Reply(_("\002{0}\002 isn't registered."), nick); + else if (result == MemoServ::MemoServService::MEMO_TOO_FAST) + source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), Config->GetModule("memoserv")->Get("senddelay"), source.command); + else if (result == MemoServ::MemoServService::MEMO_TARGET_FULL) + source.Reply(_("Sorry, \002{0}\002 currently has too many memos and cannot receive more."), nick); + else + { + source.Reply(_("Memo sent to \002{0}\002."), nick); + + bool ischan, isregistered; + MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(nick, ischan, isregistered, false); + if (mi == NULL) + throw CoreException("NULL mi in ms_rsend"); + MemoServ::Memo *m = (mi->memos->size() ? mi->GetMemo(mi->memos->size() - 1) : NULL); + if (m != NULL) + m->receipt = true; + } + } +#endif + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { +#if 0 + 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 he/she has 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 him/her that the memo\n" + "has been read.")); + return true; +#endif + } +}; + +class MSRSend : public Module +{ + CommandMSRSend commandmsrsend; + + public: + MSRSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmsrsend(this) + { + if (!MemoServ::service) + throw ModuleException("No MemoServ!"); + throw ModuleException("XXX"); + } +}; + +MODULE_INIT(MSRSend) diff --git a/modules/memoserv/send.cpp b/modules/memoserv/send.cpp new file mode 100644 index 000000000..0d26b8684 --- /dev/null +++ b/modules/memoserv/send.cpp @@ -0,0 +1,81 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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" +#include "modules/memoserv.h" + +class CommandMSSend : public Command +{ + public: + CommandMSSend(Module *creator) : Command(creator, "memoserv/send", 2, 2) + { + this->SetDesc(_("Send a memo to a nick or channel")); + this->SetSyntax(_("{\037user\037 | \037channel\037} \037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!MemoServ::service) + return; + + const Anope::string &nick = params[0]; + const Anope::string &text = params[1]; + + if (Anope::ReadOnly && !source.IsOper()) + { + source.Reply(_("Sorry, memo sending is temporarily disabled.")); + return; + } + + if (source.GetAccount()->HasFieldS("UNCONFIRMED")) + { + source.Reply(_("You must confirm your account before you may send a memo.")); + return; + } + + MemoServ::MemoServService::MemoResult result = MemoServ::service->Send(source.GetNick(), nick, text); + if (result == MemoServ::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 == MemoServ::MemoServService::MEMO_INVALID_TARGET) + source.Reply(_("\002{0}\002 is not a registered unforbidden nick or channel."), nick); + else if (result == MemoServ::MemoServService::MEMO_TOO_FAST) + source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), Config->GetModule("memoserv")->Get("senddelay"), source.command); + else if (result == MemoServ::MemoServService::MEMO_TARGET_FULL) + source.Reply(_("Sorry, \002{0}\002 currently has too many memos and cannot receive more."), nick); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends the named \037user\037 or \037channel\037 a memo containing \037memo-text\037." + " The recipient will receive a notice that they have a new memo." + " The target user or channel must be registered.")); + return true; + } +}; + +class MSSend : public Module +{ + CommandMSSend commandmssend; + + public: + MSSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmssend(this) + { + + if (!MemoServ::service) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSSend) diff --git a/modules/memoserv/sendall.cpp b/modules/memoserv/sendall.cpp new file mode 100644 index 000000000..7c5365c4d --- /dev/null +++ b/modules/memoserv/sendall.cpp @@ -0,0 +1,60 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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" +#include "modules/memoserv.h" + +class CommandMSSendAll : 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 ¶ms) override + { + if (!MemoServ::service) + return; + + const Anope::string &text = params[0]; + + Log(LOG_ADMIN, source, this) << "to send " << text; + + for (NickServ::Account *nc : NickServ::service->GetAccountList()) + if (nc != source.nc) + MemoServ::service->Send(source.GetNick(), nc->GetDisplay(), text); + + source.Reply(_("A mass memo has been sent to all registered users.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends all registered users a memo containing \037memo-text\037.")); + return true; + } +}; + +class MSSendAll : public Module +{ + CommandMSSendAll commandmssendall; + + public: + MSSendAll(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmssendall(this) + { + if (!MemoServ::service) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSSendAll) diff --git a/modules/memoserv/set.cpp b/modules/memoserv/set.cpp new file mode 100644 index 000000000..c924d0ea0 --- /dev/null +++ b/modules/memoserv/set.cpp @@ -0,0 +1,314 @@ +/* MemoServ core functions + * + * (C) 2003-2014 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 : public Command +{ + private: + void DoNotify(CommandSource &source, const std::vector ¶ms, MemoServ::MemoInfo *mi) + { + const Anope::string ¶m = params[1]; + NickServ::Account *nc = source.nc; + ServiceBot *MemoServ = Config->GetClient("MemoServ"); + + if (!MemoServ) + return; + + if (param.equals_ci("ON")) + { + nc->SetS("MEMO_SIGNON", true); + nc->SetS("MEMO_RECEIVE", true); + source.Reply(_("\002{0}\002 will now notify you of memos when you log on and when they are sent to you."), MemoServ->nick); + } + else if (param.equals_ci("LOGON")) + { + nc->SetS("MEMO_SIGNON", true); + nc->UnsetS("MEMO_RECEIVE"); + source.Reply(_("\002{0}\002 will now notify you of memos when you log on or unset /AWAY."), MemoServ->nick); + } + else if (param.equals_ci("NEW")) + { + nc->UnsetS("MEMO_SIGNON"); + nc->SetS("MEMO_RECEIVE", true); + source.Reply(_("\002{0}\002 will now notify you of memos when they are sent to you."), MemoServ->nick); + } + else if (param.equals_ci("MAIL")) + { + if (!nc->GetEmail().empty()) + { + nc->SetS("MEMO_MAIL", true); + 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->UnsetS("MEMO_MAIL"); + source.Reply(_("You will no longer be informed via email.")); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetS("MEMO_SIGNON"); + nc->UnsetS("MEMO_RECEIVE"); + nc->UnsetS("MEMO_MAIL"); + source.Reply(_("\002{0}\002 will not send you any notification of memos."), MemoServ->nick); + } + else + this->OnSyntaxError(source, ""); + } + + void DoLimit(CommandSource &source, const std::vector ¶ms, MemoServ::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; + NickServ::Account *nc = source.nc; + ChanServ::Channel *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 = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!is_servadmin && !source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + mi = ci->GetMemos(); + } + if (is_servadmin) + { + if (!p2.empty() && !