diff --git a/include/language.h b/include/language.h index d2c8cc65b..7762dd8a8 100644 --- a/include/language.h +++ b/include/language.h @@ -142,6 +142,10 @@ namespace Language #define CHAN_ACCESS_LEVEL_RANGE _("Access level must be between %d and %d inclusive.") #define CHAN_ACCESS_MALFORMED _("You cannot add a malformed mask to an access list. Did you mean to add %s instead?") #define CHAN_ACCESS_FOREIGN N_("%u access entry from other access systems not shown; use \002%s\033ALL\002 to view all access entries.", "%u access entries from other access systems not shown; use \002%s\033ALL\002 to view all access entries.") +#define CHAN_ACCESS_MIGRATED_1 _("\002%s\002 has been migrated to the %s access system.") +#define CHAN_ACCESS_MIGRATED_N N_("\002%u\002 entry has been migrated to the %s access system.", "\002%u\002 entries have been migrated to the %s access system.") +#define CHAN_ACCESS_NOT_MIGRATED_1 _("\002%s\002 can not be migrated to the %s access system because they have privileges that you do not.") +#define CHAN_ACCESS_NOT_MIGRATED_N N_("\002%u\002 entry can not be migrated to the %s access system because they have privileges that you do not.", "\002%u\002 entries can not be migrated to the %s access system because they have privileges that you do not.") #define CHAN_EXCEPTED _("\002%s\002 matches an except on %s and cannot be banned until the except has been removed.") #define CHAN_INFO_HEADER _("Information about channel \002%s\002:") #define CHAN_LIMIT_EXCEEDED _("You have already exceeded your limit of \002%d\002 channels.") diff --git a/include/modules.h b/include/modules.h index 5e7774f5e..0c02f3c3e 100644 --- a/include/modules.h +++ b/include/modules.h @@ -601,15 +601,17 @@ public: * @param ci The channel * @param source The source of the command * @param access The access entry that was removed + * @param migrated Whether the access entry was deleted because of being migrated to another system. */ - virtual void OnAccessDel(ChannelInfo *ci, CommandSource &source, ChanAccess *access) ATTR_NOT_NULL(2, 4) { throw NotImplementedException(); } + virtual void OnAccessDel(ChannelInfo *ci, CommandSource &source, ChanAccess *access, bool migrated) ATTR_NOT_NULL(2, 4) { throw NotImplementedException(); } /** Called when access is added * @param ci The channel * @param source The source of the command * @param access The access changed + * @param migrated Whether the access entry was added because of being migrated to another system. */ - virtual void OnAccessAdd(ChannelInfo *ci, CommandSource &source, ChanAccess *access) ATTR_NOT_NULL(2, 4) { throw NotImplementedException(); } + virtual void OnAccessAdd(ChannelInfo *ci, CommandSource &source, ChanAccess *access, bool migrated) ATTR_NOT_NULL(2, 4) { throw NotImplementedException(); } /** Called when the access list is cleared * @param ci The channel diff --git a/language/anope.en_US.po b/language/anope.en_US.po index 7836e5663..2f344f6cd 100644 --- a/language/anope.en_US.po +++ b/language/anope.en_US.po @@ -16,8 +16,8 @@ msgid "" msgstr "" "Project-Id-Version: Anope\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-16 19:04+0000\n" -"PO-Revision-Date: 2026-03-16 19:04+0000\n" +"POT-Creation-Date: 2026-03-16 19:13+0000\n" +"PO-Revision-Date: 2026-03-16 19:13+0000\n" "Last-Translator: Sadie Powell \n" "Language-Team: English\n" "Language: en_US\n" @@ -109,6 +109,10 @@ msgstr "" msgid "%s already exists on the EXCEPTION list." msgstr "" +#, c-format +msgid "%s can not be migrated to the %s access system because they have privileges that you do not." +msgstr "" + #, c-format msgid "%s cannot be taken as times to ban." msgstr "" @@ -173,6 +177,10 @@ msgstr "" msgid "%s has been joined to %s." msgstr "" +#, c-format +msgid "%s has been migrated to the %s access system." +msgstr "" + #, c-format msgid "%s has been parted from %s." msgstr "" @@ -340,6 +348,18 @@ msgstr "" msgid "%s will now permanently be ignored." msgstr "" +#, c-format +msgid "%u entry can not be migrated to the %s access system because they have privileges that you do not." +msgid_plural "%u entries can not be migrated to the %s access system because they have privileges that you do not." +msgstr[0] "" +msgstr[1] "" + +#, c-format +msgid "%u entry has been migrated to the %s access system." +msgid_plural "%u entries have been migrated to the %s access system." +msgstr[0] "" +msgstr[1] "" + msgid "ADD nick user host real" msgstr "" @@ -540,6 +560,9 @@ msgstr "" msgid "channel LOCK {ADD|DEL|SET|LIST} [what]" msgstr "" +msgid "channel MIGRATE [mask]" +msgstr "" + msgid "channel PREPEND topic" msgstr "" diff --git a/modules/chanserv/cs_access.cpp b/modules/chanserv/cs_access.cpp index 2043adc79..ba77782d7 100644 --- a/modules/chanserv/cs_access.cpp +++ b/modules/chanserv/cs_access.cpp @@ -296,7 +296,7 @@ private: access->description = description; ci->AddAccess(access); - FOREACH_MOD(OnAccessAdd, (ci, source, access)); + FOREACH_MOD(OnAccessAdd, (ci, source, access, false)); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask << " with level " << level; if (p != NULL) @@ -387,7 +387,7 @@ private: ci->EraseAccess(Number - 1); - FOREACH_MOD(OnAccessDel, (ci, source, access)); + FOREACH_MOD(OnAccessDel, (ci, source, access, false)); delete access; } } @@ -413,7 +413,7 @@ private: Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << access->Mask(); ci->EraseAccess(i - 1); - FOREACH_MOD(OnAccessDel, (ci, source, access)); + FOREACH_MOD(OnAccessDel, (ci, source, access, false)); delete access; } return; diff --git a/modules/chanserv/cs_flags.cpp b/modules/chanserv/cs_flags.cpp index bfd77b5b8..10099d7d3 100644 --- a/modules/chanserv/cs_flags.cpp +++ b/modules/chanserv/cs_flags.cpp @@ -286,7 +286,7 @@ class CommandCSFlags final if (current != NULL) { ci->EraseAccess(current_idx - 1); - FOREACH_MOD(OnAccessDel, (ci, source, current)); + FOREACH_MOD(OnAccessDel, (ci, source, current, false)); delete current; Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << mask; source.Reply(_("\002%s\002 removed from the %s access list."), mask.c_str(), ci->name.c_str()); @@ -314,7 +314,7 @@ class CommandCSFlags final ci->AddAccess(access); - FOREACH_MOD(OnAccessAdd, (ci, source, access)); + FOREACH_MOD(OnAccessAdd, (ci, source, access, false)); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to modify " << mask << "'s flags to " << access->AccessSerialize(); if (p != NULL) @@ -408,6 +408,80 @@ class CommandCSFlags final } } + void DoMigrate(CommandSource &source, ChannelInfo *ci, const std::vector ¶ms) + { + auto override = false; + const auto source_access = source.AccessFor(ci); + + unsigned migrated = 0, notmigrated = 0; + Anope::string migratedmask, notmigratedmask; + const auto &entry = params.size() > 2 ? params[2] : "*"; + for (auto idx = ci->GetAccessCount(); idx > 0; --idx) + { + auto *access = ci->GetAccess(idx - 1); + if (access->provider->name == "access/flags") + continue; // Already using flags. + + if (!Anope::Match(access->Mask(), entry)) + continue; // Not this entry. + + std::set newflags; + for (auto &[priv, flag] : defaultFlags) + { + if (access->HasPriv(priv)) + continue; // Source doesn't have this flag. + + // Check that the source has access to set this entry. + if (!override && !source_access.HasPriv(priv) && !source_access.founder) + { + if (!source.HasPriv("chanserv/access/modify")) + { + notmigrated++; + notmigratedmask = access->Mask(); + continue; // No privs + } + + override = true; + } + + newflags.insert(flag); + } + + migrated++; + migratedmask = access->Mask(); + + auto *newaccess = anope_dynamic_static_cast(FlagsAccessProvider::ap->Create()); + newaccess->SetMask(access->Mask(), ci); + newaccess->creator = access->creator; + newaccess->description = access->description; + newaccess->created = access->created; + newaccess->flags = newflags; + + ci->EraseAccess(idx - 1); + FOREACH_MOD(OnAccessDel, (ci, source, access, true)); + delete access; + + ci->AddAccess(newaccess); + FOREACH_MOD(OnAccessAdd, (ci, source, newaccess, true)); + } + + if (migrated == 1) + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to migrate " << migratedmask; + source.Reply(CHAN_ACCESS_MIGRATED_1, migratedmask.c_str(), source.command.nobreak().c_str()); + } + else if (migrated > 1) + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to migrate " << migrated << " access entries"; + source.Reply(migrated, CHAN_ACCESS_MIGRATED_N, migrated, source.command.nobreak().c_str()); + } + + if (notmigrated == 1) + source.Reply(CHAN_ACCESS_NOT_MIGRATED_1, notmigratedmask.c_str(), source.command.nobreak().c_str()); + else if (notmigrated > 1) + source.Reply(migrated, CHAN_ACCESS_NOT_MIGRATED_N, notmigrated, source.command.nobreak().c_str()); + } + void DoClear(CommandSource &source, ChannelInfo *ci) { if (!source.IsFounder(ci) && !source.HasPriv("chanserv/access/modify")) @@ -431,6 +505,7 @@ public: this->SetDesc(_("Modify the list of privileged users")); this->SetSyntax(_("\037channel\037 [MODIFY] \037mask\037 \037changes\037 [\037description\037]")); this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | +\037flags\037] [ALL]")); + this->SetSyntax(_("\037channel\037 MIGRATE [\037mask\037]")); this->SetSyntax(_("\037channel\037 CLEAR")); } @@ -463,6 +538,8 @@ public: source.Reply(READ_ONLY_MODE); else if (is_list) this->DoList(source, ci, params); + else if (cmd.equals_ci("MIGRATE")) + this->DoMigrate(source, ci, params); else if (cmd.equals_ci("CLEAR")) this->DoClear(source, ci); else diff --git a/modules/chanserv/cs_statusupdate.cpp b/modules/chanserv/cs_statusupdate.cpp index ac5646e91..998dc18a8 100644 --- a/modules/chanserv/cs_statusupdate.cpp +++ b/modules/chanserv/cs_statusupdate.cpp @@ -18,9 +18,9 @@ class StatusUpdate final : public Module { private: - void OnAccessChange(ChannelInfo *ci, ChanAccess *access, bool adding) + void OnAccessChange(ChannelInfo *ci, ChanAccess *access, bool migrated, bool adding) { - if (!ci->c) + if (!ci->c || migrated) return; for (const auto &[_, uc] : ci->c->users) @@ -50,14 +50,14 @@ public: { } - void OnAccessAdd(ChannelInfo *ci, CommandSource &, ChanAccess *access) override + void OnAccessAdd(ChannelInfo *ci, CommandSource &, ChanAccess *access, bool migrated) override { - OnAccessChange(ci, access, true); + OnAccessChange(ci, access, migrated, true); } - void OnAccessDel(ChannelInfo *ci, CommandSource &, ChanAccess *access) override + void OnAccessDel(ChannelInfo *ci, CommandSource &, ChanAccess *access, bool migrated) override { - OnAccessChange(ci, access, false); + OnAccessChange(ci, access, migrated, false); } }; diff --git a/modules/chanserv/cs_xop.cpp b/modules/chanserv/cs_xop.cpp index 70db3d43f..cd90bf180 100644 --- a/modules/chanserv/cs_xop.cpp +++ b/modules/chanserv/cs_xop.cpp @@ -263,7 +263,7 @@ private: Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask; - FOREACH_MOD(OnAccessAdd, (ci, source, acc)); + FOREACH_MOD(OnAccessAdd, (ci, source, acc, false)); source.Reply(_("\002%s\002 added to %s %s list."), acc->Mask().c_str(), ci->name.c_str(), source.command.nobreak().c_str()); } @@ -372,7 +372,7 @@ private: nicks += caccess->Mask(); ci->EraseAccess(number - 1); - FOREACH_MOD(OnAccessDel, (ci, source, caccess)); + FOREACH_MOD(OnAccessDel, (ci, source, caccess, false)); delete caccess; } } @@ -395,7 +395,7 @@ private: source.Reply(_("\002%s\002 deleted from %s %s list."), a->Mask().c_str(), ci->name.c_str(), source.command.nobreak().c_str()); ci->EraseAccess(i); - FOREACH_MOD(OnAccessDel, (ci, source, a)); + FOREACH_MOD(OnAccessDel, (ci, source, a, false)); delete a; return;