summaryrefslogtreecommitdiff
path: root/modules/nickserv/ns_ajoin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/nickserv/ns_ajoin.cpp')
-rw-r--r--modules/nickserv/ns_ajoin.cpp406
1 files changed, 406 insertions, 0 deletions
diff --git a/modules/nickserv/ns_ajoin.cpp b/modules/nickserv/ns_ajoin.cpp
new file mode 100644
index 000000000..5e4419b85
--- /dev/null
+++ b/modules/nickserv/ns_ajoin.cpp
@@ -0,0 +1,406 @@
+/* NickServ 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"
+
+struct AJoinEntry;
+
+struct AJoinList final
+ : Serialize::Checker<std::vector<AJoinEntry *> >
+{
+ AJoinList(Extensible *) : Serialize::Checker<std::vector<AJoinEntry *> >("AJoinEntry") { }
+ ~AJoinList();
+};
+
+struct AJoinEntry final
+ : Serializable
+{
+ Serialize::Reference<NickCore> owner;
+ Anope::string channel;
+ Anope::string key;
+
+ AJoinEntry(Extensible *) : Serializable("AJoinEntry") { }
+
+ ~AJoinEntry() override
+ {
+ AJoinList *channels = owner->GetExt<AJoinList>("ajoinlist");
+ if (channels)
+ {
+ std::vector<AJoinEntry *>::iterator it = std::find((*channels)->begin(), (*channels)->end(), this);
+ if (it != (*channels)->end())
+ (*channels)->erase(it);
+ }
+ }
+
+ void Serialize(Serialize::Data &data) const override
+ {
+ if (!this->owner)
+ return;
+
+ data.Store("owner", this->owner->display);
+ data.Store("channel", this->channel);
+ data.Store("key", this->key);
+ }
+
+ static Serializable *Unserialize(Serializable *obj, Serialize::Data &sd)
+ {
+ Anope::string sowner;
+
+ sd["owner"] >> sowner;
+
+ NickCore *nc = NickCore::Find(sowner);
+ if (nc == NULL)
+ return NULL;
+
+ AJoinEntry *aj;
+ if (obj)
+ aj = anope_dynamic_static_cast<AJoinEntry *>(obj);
+ else
+ {
+ aj = new AJoinEntry(nc);
+ aj->owner = nc;
+ }
+
+ sd["channel"] >> aj->channel;
+ sd["key"] >> aj->key;
+
+ if (!obj)
+ {
+ AJoinList *channels = nc->Require<AJoinList>("ajoinlist");
+ (*channels)->push_back(aj);
+ }
+
+ return aj;
+ }
+};
+
+AJoinList::~AJoinList()
+{
+ for (const auto *ajoin : *(*this))
+ delete ajoin;
+}
+
+class CommandNSAJoin final
+ : public Command
+{
+ static void DoList(CommandSource &source, NickCore *nc)
+ {
+ AJoinList *channels = nc->Require<AJoinList>("ajoinlist");
+
+ if ((*channels)->empty())
+ source.Reply(_("%s's auto join list is empty."), nc->display.c_str());
+ else
+ {
+ ListFormatter list(source.GetAccount());
+ list.AddColumn(_("Number")).AddColumn(_("Channel")).AddColumn(_("Key"));
+ for (unsigned i = 0; i < (*channels)->size(); ++i)
+ {
+ AJoinEntry *aj = (*channels)->at(i);
+ ListFormatter::ListEntry entry;
+ entry["Number"] = Anope::ToString(i + 1);
+ entry["Channel"] = aj->channel;
+ entry["Key"] = aj->key;
+ list.AddEntry(entry);
+ }
+
+ source.Reply(_("%s's auto join list:"), nc->display.c_str());
+
+ std::vector<Anope::string> replies;
+ list.Process(replies);
+
+ for (const auto &reply : replies)
+ source.Reply(reply);
+ }
+ }
+
+ void DoAdd(CommandSource &source, NickCore *nc, const Anope::string &chans, const Anope::string &keys)
+ {
+ AJoinList *channels = nc->Require<AJoinList>("ajoinlist");
+
+ Anope::string addedchans;
+ Anope::string alreadyadded;
+ Anope::string invalidkey;
+ commasepstream ksep(keys, true);
+ commasepstream csep(chans);
+ for (Anope::string chan, key; csep.GetToken(chan);)
+ {
+ ksep.GetToken(key);
+
+ unsigned i = 0;
+ for (; i < (*channels)->size(); ++i)
+ if ((*channels)->at(i)->channel.equals_ci(chan))
+ break;
+
+ if ((*channels)->size() >= Config->GetModule(this->owner)->Get<unsigned>("ajoinmax"))
+ {
+ source.Reply(_("Sorry, the maximum of %d auto join entries has been reached."), Config->GetModule(this->owner)->Get<unsigned>("ajoinmax"));
+ return;
+ }
+ else if (i != (*channels)->size())
+ alreadyadded += chan + ", ";
+ else if (!IRCD->IsChannelValid(chan))
+ source.Reply(CHAN_X_INVALID, chan.c_str());
+ else
+ {
+ Channel *c = Channel::Find(chan);
+ Anope::string k;
+ if (c && c->GetParam("KEY", k) && key != k)
+ {
+ invalidkey += chan + ", ";
+ continue;
+ }
+
+ auto *entry = new AJoinEntry(nc);
+ entry->owner = nc;
+ entry->channel = chan;
+ entry->key = key;
+ (*channels)->push_back(entry);
+ addedchans += chan + ", ";
+ }
+ }
+
+ if (!alreadyadded.empty())
+ {
+ alreadyadded = alreadyadded.substr(0, alreadyadded.length() - 2);
+ source.Reply(_("%s is already on %s's auto join list."), alreadyadded.c_str(), nc->display.c_str());
+ }
+
+ if (!invalidkey.empty())
+ {
+ invalidkey = invalidkey.substr(0, invalidkey.length() - 2);
+ source.Reply(_("%s had an invalid key specified, and was thus ignored."), invalidkey.c_str());
+ }
+
+ if (addedchans.empty())
+ return;
+
+ addedchans = addedchans.substr(0, addedchans.length() - 2);
+ Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD channel " << addedchans << " to " << nc->display;
+ source.Reply(_("%s added to %s's auto join list."), addedchans.c_str(), nc->display.c_str());
+ }
+
+ void DoDel(CommandSource &source, NickCore *nc, const Anope::string &chans)
+ {
+ AJoinList *channels = nc->Require<AJoinList>("ajoinlist");
+ Anope::string delchans;
+ Anope::string notfoundchans;
+ commasepstream sep(chans);
+
+ for (Anope::string chan; sep.GetToken(chan);)
+ {
+ unsigned i = 0;
+ for (; i < (*channels)->size(); ++i)
+ if ((*channels)->at(i)->channel.equals_ci(chan))
+ break;
+
+ if (i == (*channels)->size())
+ notfoundchans += chan + ", ";
+ else
+ {
+ delete (*channels)->at(i);
+ delchans += chan + ", ";
+ }
+ }
+
+ if (!notfoundchans.empty())
+ {
+ notfoundchans = notfoundchans.substr(0, notfoundchans.length() - 2);
+ source.Reply(_("%s was not found on %s's auto join list."), notfoundchans.c_str(), nc->display.c_str());
+ }
+
+ if (delchans.empty())
+ return;
+
+ delchans = delchans.substr(0, delchans.length() - 2);
+ Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE channel " << delchans << " from " << nc->display;
+ source.Reply(_("%s was removed from %s's auto join list."), delchans.c_str(), nc->display.c_str());
+
+ if ((*channels)->empty())
+ nc->Shrink<AJoinList>("ajoinlist");
+ }
+
+public:
+ CommandNSAJoin(Module *creator) : Command(creator, "nickserv/ajoin", 1, 4)
+ {
+ this->SetDesc(_("Manage your auto join list"));
+ this->SetSyntax(_("ADD [\037nickname\037] \037channel\037 [\037key\037]"));
+ this->SetSyntax(_("DEL [\037nickname\037] \037channel\037"));
+ this->SetSyntax(_("LIST [\037nickname\037]"));
+ }
+
+ void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
+ {
+ const Anope::string &cmd = params[0];
+ Anope::string nick, param, param2;
+
+ if (cmd.equals_ci("LIST"))
+ nick = params.size() > 1 ? params[1] : "";
+ else
+ nick = (params.size() > 2 && IRCD->IsChannelValid(params[2])) ? params[1] : "";
+
+ NickCore *nc;
+ if (!nick.empty())
+ {
+ const NickAlias *na = NickAlias::Find(nick);
+ if (na == NULL)
+ {
+ source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
+ return;
+ }
+ else if (na->nc != source.GetAccount() && !source.HasCommand("nickserv/ajoin"))
+ {
+ source.Reply(ACCESS_DENIED);
+ return;
+ }
+
+ nc = na->nc;
+ param = params.size() > 2 ? params[2] : "";
+ param2 = params.size() > 3 ? params[3] : "";
+ }
+ else
+ {
+ nc = source.nc;
+ param = params.size() > 1 ? params[1] : "";
+ param2 = params.size() > 2 ? params[2] : "";
+ }
+
+ if (cmd.equals_ci("LIST"))
+ return this->DoList(source, nc);
+ else if (nc->HasExt("NS_SUSPENDED"))
+ source.Reply(NICK_X_SUSPENDED, nc->display.c_str());
+ else if (param.empty())
+ this->OnSyntaxError(source, "");
+ else if (Anope::ReadOnly)
+ source.Reply(READ_ONLY_MODE);
+ else if (cmd.equals_ci("ADD"))
+ return this->DoAdd(source, nc, param, param2);
+ else if (cmd.equals_ci("DEL"))
+ return this->DoDel(source, nc, param);
+ else
+ this->OnSyntaxError(source, "");
+ }
+
+ bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
+ {
+ this->SendSyntax(source);
+ source.Reply(" ");
+ source.Reply(_("This command manages your auto join list. When you identify\n"
+ "you will automatically join the channels on your auto join list.\n"
+ "Services Operators may provide a nick to modify other users'\n"
+ "auto join lists."));
+ return true;
+ }
+};
+
+class NSAJoin final
+ : public Module
+{
+ CommandNSAJoin commandnsajoin;
+ ExtensibleItem<AJoinList> ajoinlist;
+ Serialize::Type ajoinentry_type;
+
+public:
+ NSAJoin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
+ commandnsajoin(this), ajoinlist(this, "ajoinlist"),
+ ajoinentry_type("AJoinEntry", AJoinEntry::Unserialize)
+ {
+
+ if (!IRCD || !IRCD->CanSVSJoin)
+ throw ModuleException("Your IRCd does not support SVSJOIN");
+
+ }
+
+ void OnUserLogin(User *u) override
+ {
+ BotInfo *NickServ = Config->GetClient("NickServ");
+ if (!NickServ)
+ return;
+
+ AJoinList *channels = u->Account()->GetExt<AJoinList>("ajoinlist");
+ if (channels == NULL)
+ return;
+
+ /* Set +r now, so we can ajoin users into +R channels */
+ ModeManager::ProcessModes();
+
+ for (auto *entry : *(*channels))
+ {
+ Channel *c = Channel::Find(entry->channel);
+ ChannelInfo *ci;
+
+ if (c)
+ ci = c->ci;
+ else
+ ci = ChannelInfo::Find(entry->channel);
+
+ bool need_invite = false;
+ Anope::string key = entry->key;
+ AccessGroup u_access;
+
+ if (ci != NULL)
+ {
+ if (ci->HasExt("CS_SUSPENDED"))
+ continue;
+ u_access = ci->AccessFor(u);
+ }
+ if (c != NULL)
+ {
+ if (c->FindUser(u) != NULL)
+ continue;
+ else if (c->HasMode("OPERONLY") && !u->HasMode("OPER"))
+ continue;
+ else if (c->HasMode("ADMINONLY") && !u->HasMode("ADMIN"))
+ continue;
+ else if (c->HasMode("SSL") && !u->IsSecurelyConnected())
+ continue;
+ else if (c->MatchesList(u, "BAN") && !c->MatchesList(u, "EXCEPT"))
+ need_invite = true;
+ else if (c->HasMode("INVITE") && !c->MatchesList(u, "INVITEOVERRIDE"))
+ need_invite = true;
+
+ if (c->HasMode("KEY"))
+ {
+ Anope::string k;
+ if (c->GetParam("KEY", k))
+ {
+ if (u_access.HasPriv("GETKEY"))
+ key = k;
+ else if (key != k)
+ need_invite = true;
+ }
+ }
+ if (c->HasMode("LIMIT"))
+ {
+ Anope::string l;
+ if (c->GetParam("LIMIT", l))
+ {
+ if (auto limit = Anope::TryConvert<unsigned>(l))
+ {
+ if (c->users.size() >= limit.value())
+ need_invite = true;
+ }
+ }
+ }
+ }
+
+ if (need_invite && c != NULL)
+ {
+ if (!u_access.HasPriv("INVITE"))
+ continue;
+ IRCD->SendInvite(NickServ, c, u);
+ }
+
+ IRCD->SendSVSJoin(NickServ, u, entry->channel, key);
+ }
+ }
+};
+
+MODULE_INIT(NSAJoin)