summaryrefslogtreecommitdiff
path: root/modules/chanserv/cs_clone.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/chanserv/cs_clone.cpp')
-rw-r--r--modules/chanserv/cs_clone.cpp262
1 files changed, 262 insertions, 0 deletions
diff --git a/modules/chanserv/cs_clone.cpp b/modules/chanserv/cs_clone.cpp
new file mode 100644
index 000000000..6f29a235c
--- /dev/null
+++ b/modules/chanserv/cs_clone.cpp
@@ -0,0 +1,262 @@
+/* ChanServ core functions
+ *
+ * (C) 2003-2025 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/bs_badwords.h"
+
+class CommandCSClone final
+ : public Command
+{
+ static void CopySetting(ChannelInfo *ci, ChannelInfo *target_ci, const Anope::string &setting)
+ {
+ if (ci->HasExt(setting))
+ target_ci->Extend<bool>(setting);
+ }
+
+ static void CopyAccess(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci)
+ {
+ std::set<Anope::string> masks;
+ unsigned access_max = Config->GetModule("chanserv")->Get<unsigned>("accessmax", "1000");
+ unsigned count = 0;
+
+ for (unsigned i = 0; i < target_ci->GetAccessCount(); ++i)
+ masks.insert(target_ci->GetAccess(i)->Mask());
+
+ for (unsigned i = 0; i < ci->GetAccessCount(); ++i)
+ {
+ const ChanAccess *taccess = ci->GetAccess(i);
+ AccessProvider *provider = taccess->provider;
+
+ if (access_max && target_ci->GetDeepAccessCount() >= access_max)
+ break;
+
+ if (masks.count(taccess->Mask()))
+ continue;
+ masks.insert(taccess->Mask());
+
+ ChanAccess *newaccess = provider->Create();
+ newaccess->SetMask(taccess->Mask(), target_ci);
+ newaccess->creator = taccess->creator;
+ newaccess->description = taccess->description;
+ newaccess->last_seen = taccess->last_seen;
+ newaccess->created = taccess->created;
+ newaccess->AccessUnserialize(taccess->AccessSerialize());
+
+ target_ci->AddAccess(newaccess);
+
+ ++count;
+ }
+
+ source.Reply(_("%d access entries from \002%s\002 have been cloned to \002%s\002."), count, ci->name.c_str(), target_ci->name.c_str());
+ }
+
+ static void CopyAkick(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci)
+ {
+ target_ci->ClearAkick();
+ for (unsigned i = 0; i < ci->GetAkickCount(); ++i)
+ {
+ const AutoKick *akick = ci->GetAkick(i);
+ if (akick->nc)
+ target_ci->AddAkick(akick->creator, akick->nc, akick->reason, akick->addtime, akick->last_used);
+ else
+ target_ci->AddAkick(akick->creator, akick->mask, akick->reason, akick->addtime, akick->last_used);
+ }
+
+ source.Reply(_("All akick entries from \002%s\002 have been cloned to \002%s\002."), ci->name.c_str(), target_ci->name.c_str());
+ }
+
+ static void CopyBadwords(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci)
+ {
+ BadWords *target_badwords = target_ci->Require<BadWords>("badwords"),
+ *badwords = ci->Require<BadWords>("badwords");
+
+ if (!target_badwords || !badwords)
+ {
+ source.Reply(ACCESS_DENIED); // BotServ doesn't exist/badwords isn't loaded
+ return;
+ }
+
+ target_badwords->ClearBadWords();
+
+ for (unsigned i = 0; i < badwords->GetBadWordCount(); ++i)
+ {
+ const BadWord *bw = badwords->GetBadWord(i);
+ target_badwords->AddBadWord(bw->word, bw->type);
+ }
+
+ badwords->Check();
+ target_badwords->Check();
+
+ source.Reply(_("All badword entries from \002%s\002 have been cloned to \002%s\002."), ci->name.c_str(), target_ci->name.c_str());
+ }
+
+ static void CopyLevels(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci)
+ {
+ for (const auto &[priv, level] : ci->GetLevelEntries())
+ {
+ target_ci->SetLevel(priv, level);
+ }
+
+ source.Reply(_("All level entries from \002%s\002 have been cloned into \002%s\002."), ci->name.c_str(), target_ci->name.c_str());
+ }
+
+public:
+ CommandCSClone(Module *creator) : Command(creator, "chanserv/clone", 2, 3)
+ {
+ this->SetDesc(_("Copy all settings from one channel to another"));
+ this->SetSyntax(_("\037channel\037 \037target\037 [\037what\037]"));
+ }
+
+ void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
+ {
+ const Anope::string &channel = params[0];
+ const Anope::string &target = params[1];
+ Anope::string what = params.size() > 2 ? params[2] : "";
+
+ if (Anope::ReadOnly)
+ {
+ source.Reply(READ_ONLY_MODE);
+ return;
+ }
+
+ User *u = source.GetUser();
+ ChannelInfo *ci = ChannelInfo::Find(channel);
+ bool override = false;
+
+ if (ci == NULL)
+ {
+ source.Reply(CHAN_X_NOT_REGISTERED, channel.c_str());
+ return;
+ }
+
+ ChannelInfo *target_ci = ChannelInfo::Find(target);
+ if (!target_ci)
+ {
+ source.Reply(CHAN_X_NOT_REGISTERED, target.c_str());
+ return;
+ }
+
+ if (ci == target_ci)
+ {
+ source.Reply(_("Cannot clone channel \002%s\002 to itself!"), target.c_str());
+ return;
+ }
+
+ if (!source.IsFounder(ci) || !source.IsFounder(target_ci))
+ {
+ if (!source.HasPriv("chanserv/administration"))
+ {
+ source.Reply(ACCESS_DENIED);
+ return;
+ }
+ else
+ override = true;
+ }
+
+ if (what.equals_ci("ALL"))
+ what.clear();
+
+ if (what.empty())
+ {
+ delete target_ci;
+ target_ci = new ChannelInfo(*ci);
+ target_ci->name = target;
+ target_ci->time_registered = Anope::CurTime;
+ (*RegisteredChannelList)[target_ci->name] = target_ci;
+ target_ci->c = Channel::Find(target_ci->name);
+
+ target_ci->bi = NULL;
+ if (ci->bi)
+ ci->bi->Assign(u, target_ci);
+
+ if (target_ci->c)
+ {
+ target_ci->c->ci = target_ci;
+
+ target_ci->c->CheckModes();
+
+ target_ci->c->SetCorrectModes(u, true);
+ }
+
+ if (target_ci->c && !target_ci->c->topic.empty())
+ {
+ target_ci->last_topic = target_ci->c->topic;
+ target_ci->last_topic_setter = target_ci->c->topic_setter;
+ target_ci->last_topic_time = target_ci->c->topic_time;
+ }
+ else
+ target_ci->last_topic_setter = source.service->nick;
+
+ const Anope::string settings[] = { "NOAUTOOP", "CS_KEEP_MODES", "PEACE", "PERSIST", "RESTRICTED",
+ "SECUREFOUNDER", "SECUREOPS", "SIGNKICK", "SIGNKICK_LEVEL", "CS_NO_EXPIRE" };
+
+ for (const auto &setting : settings)
+ CopySetting(ci, target_ci, setting);
+
+ CopyAccess(source, ci, target_ci);
+ CopyAkick(source, ci, target_ci);
+ CopyBadwords(source, ci, target_ci);
+ CopyLevels(source, ci, target_ci);
+
+ FOREACH_MOD(OnChanRegistered, (target_ci));
+
+ source.Reply(_("All settings from \002%s\002 have been cloned to \002%s\002."), ci->name.c_str(), target_ci->name.c_str());
+ }
+ else if (what.equals_ci("ACCESS"))
+ {
+ CopyAccess(source, ci, target_ci);
+ }
+ else if (what.equals_ci("AKICK"))
+ {
+ CopyAkick(source, ci, target_ci);
+ }
+ else if (what.equals_ci("BADWORDS"))
+ {
+ CopyBadwords(source, ci, target_ci);
+ }
+ else if (what.equals_ci("LEVELS"))
+ {
+ CopyLevels(source, ci, target_ci);
+ }
+ else
+ {
+ this->OnSyntaxError(source, "");
+ return;
+ }
+
+ Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clone " << (what.empty() ? "everything from it" : what) << " to " << target_ci->name;
+ }
+
+ bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
+ {
+ this->SendSyntax(source);
+ source.Reply(" ");
+ source.Reply(_("Copies all settings, access, akicks, etc from \002channel\002 to the\n"
+ "\002target\002 channel. If \037what\037 is \002ACCESS\002, \002AKICK\002, \002BADWORDS\002,\n"
+ "or \002LEVELS\002 then only the respective settings are cloned.\n"
+ "You must be the founder of \037channel\037 and \037target\037."));
+ return true;
+ }
+};
+
+class CSClone final
+ : public Module
+{
+ CommandCSClone commandcsclone;
+
+public:
+ CSClone(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), commandcsclone(this)
+ {
+
+ }
+};
+
+MODULE_INIT(CSClone)