Skip to content

Commit eb3580a

Browse files
committed
chanfix update & m_expirenotice fix
1 parent 366fb23 commit eb3580a

File tree

4 files changed

+65
-24
lines changed

4 files changed

+65
-24
lines changed

modules/third/ChanFix/chanfix.example.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ module
6464
/* If enabled, ChanFix will clear channel bans on a successful fix. */
6565
clear_bans_on_fix = yes
6666

67+
/* Optional: remove +o from users who do not meet the score threshold
68+
* during a fix. This is more aggressive and can deop legitimate new users
69+
* who lack history, but helps reverse takeovers where a low-history user
70+
* retains ops while ChanFix is running.
71+
*/
72+
deop_below_threshold_on_fix = no
73+
6774
/* Algorithm parameters. */
6875
op_threshold = 3
6976
min_fix_score = 12

modules/third/ChanFix/chanfix.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class ChanFixCore final
108108
bool clear_modes_on_fix = false;
109109
bool clear_bans_on_fix = false;
110110
bool clear_moderated_on_fix = false;
111+
bool deop_below_threshold_on_fix = false;
111112

112113
unsigned int op_threshold = 3;
113114
unsigned int min_fix_score = 12;

modules/third/ChanFix/chanfix_core.cpp

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -552,24 +552,32 @@ bool ChanFixCore::FixChannel(CFChannelData& rec, Channel* c)
552552

553553
const unsigned int threshold = this->GetThreshold(rec, Anope::CurTime);
554554
unsigned int opped = 0;
555+
unsigned int good_ops_already_present = 0;
556+
std::vector<User*> to_deop;
555557
const bool already_in_chan = c->FindUser(this->chanfix) != NULL;
556558
bool joined = already_in_chan;
557559

558560
for (const auto& [u, cuc] : c->users)
559561
{
560562
if (!u || !cuc || u == this->chanfix)
561563
continue;
562-
if (cuc->status.HasMode(this->op_status_char))
563-
continue;
564564

565-
const CFOpRecord* orec = nullptr;
566-
auto it = rec.oprecords.find(this->KeyForUser(u));
567-
if (it != rec.oprecords.end())
568-
orec = &it->second;
569-
if (!orec)
565+
const bool is_opped = cuc->status.HasMode(this->op_status_char);
566+
unsigned int score = 0;
567+
if (auto it = rec.oprecords.find(this->KeyForUser(u)); it != rec.oprecords.end())
568+
score = this->CalculateScore(it->second);
569+
const bool should_be_op = (score >= threshold);
570+
571+
if (is_opped)
572+
{
573+
if (should_be_op)
574+
++good_ops_already_present;
575+
else if (this->deop_below_threshold_on_fix)
576+
to_deop.push_back(u);
570577
continue;
578+
}
571579

572-
if (this->CalculateScore(*orec) < threshold)
580+
if (!should_be_op)
573581
continue;
574582

575583
if (this->join_to_fix && !joined)
@@ -582,7 +590,19 @@ bool ChanFixCore::FixChannel(CFChannelData& rec, Channel* c)
582590
opped++;
583591
}
584592

585-
if (opped == 0)
593+
// Only deop if at least one "good" op will remain (either already present or newly opped).
594+
const unsigned int good_ops_after = good_ops_already_present + opped;
595+
if (this->deop_below_threshold_on_fix && good_ops_after > 0)
596+
{
597+
for (User* u : to_deop)
598+
{
599+
if (!u)
600+
continue;
601+
c->RemoveMode(this->chanfix, "OP", u->GetUID(), false);
602+
}
603+
}
604+
605+
if (good_ops_after == 0)
586606
return false;
587607

588608
if (this->clear_modes_on_fix)
@@ -702,6 +722,7 @@ void ChanFixCore::OnReload(Configuration::Conf& conf)
702722
this->clear_modes_on_fix = mod->Get<bool>("clear_modes_on_fix", "no");
703723
this->clear_bans_on_fix = mod->Get<bool>("clear_bans_on_fix", "no");
704724
this->clear_moderated_on_fix = mod->Get<bool>("clear_moderated_on_fix", "no");
725+
this->deop_below_threshold_on_fix = mod->Get<bool>("deop_below_threshold_on_fix", "no");
705726

706727
this->op_threshold = mod->Get<unsigned int>("op_threshold", "3");
707728
this->min_fix_score = mod->Get<unsigned int>("min_fix_score", "12");

modules/third/m_expirenotice.cpp

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ module
5858
*/
5959

6060
#include "module.h"
61+
#include "mail.h"
6162
#include "modules/memoserv/service.h"
6263

6364
class ExpireNotice : public Module
@@ -119,13 +120,14 @@ class ExpireNotice : public Module
119120

120121
if (ns_notice_mail && !na->nc->email.empty())
121122
{
122-
Anope::string subject = Config->GetModule(this).Get<Anope::string>("ns_expiring_subject"),
123-
message = Config->GetModule(this).Get<Anope::string>("ns_expiring_message");
123+
Anope::string subject = Config->GetModule(this).Get<Anope::string>("ns_expiring_subject", "Nickname expiring"),
124+
message = Config->GetModule(this).Get<Anope::string>("ns_expiring_message", "Your nickname %n will expire %t.\n%N IRC Administration");
124125
message = message.replace_all_cs("%n", na->nick);
125126
message = message.replace_all_cs("%t", Anope::strftime(expire_at, na->nc));
126127
message = message.replace_all_cs("%N", networkname);
127128

128-
Mail::Send(na->nc, subject, message);
129+
if (!Mail::Send(na->nc, subject, message))
130+
Log(LOG_DEBUG) << "m_expirenotice: failed to send expiring mail to " << na->nc->display;
129131
}
130132
/* If the NickCore has more than one NickAlias (not all expiring right now), send a memo */
131133
if (ns_notice_memo && (*na->nc->aliases).size() > 1 && !AllAliasesExpiring(na->nc))
@@ -152,12 +154,13 @@ class ExpireNotice : public Module
152154

153155
if (ns_notice_mail && !na->nc->email.empty())
154156
{
155-
Anope::string subject = Config->GetModule(this).Get<Anope::string>("ns_expired_subject"),
156-
message = Config->GetModule(this).Get<Anope::string>("ns_expired_message");
157+
Anope::string subject = Config->GetModule(this).Get<Anope::string>("ns_expired_subject", "Nickname expired"),
158+
message = Config->GetModule(this).Get<Anope::string>("ns_expired_message", "Your nickname %n has expired.\n%N IRC Administration");
157159
message = message.replace_all_cs("%n", na->nick);
158160
message = message.replace_all_cs("%N", networkname);
159161

160-
Mail::Send(na->nc, subject, message);
162+
if (!Mail::Send(na->nc, subject, message))
163+
Log(LOG_DEBUG) << "m_expirenotice: failed to send expired mail to " << na->nc->display;
161164
}
162165
/* If the NickCore has more than one NickAlias (not all expiring right now), send a memo */
163166
if (ns_notice_memo && (*na->nc->aliases).size() > 1 && !AllAliasesExpiring(na->nc))
@@ -214,8 +217,8 @@ class ExpireNotice : public Module
214217

215218
if (cs_notice_mail)
216219
{
217-
Anope::string subject = Config->GetModule(this).Get<Anope::string>("cs_expiring_subject"),
218-
base_message = Config->GetModule(this).Get<Anope::string>("cs_expiring_message");
220+
Anope::string subject = Config->GetModule(this).Get<Anope::string>("cs_expiring_subject", "Channel expiring"),
221+
base_message = Config->GetModule(this).Get<Anope::string>("cs_expiring_message", "Your channel %c will expire %t.\n%N IRC Administration");
219222
base_message = base_message.replace_all_cs("%c", ci->name);
220223
base_message = base_message.replace_all_cs("%N", networkname);
221224

@@ -224,14 +227,16 @@ class ExpireNotice : public Module
224227
Anope::string message = base_message;
225228
message = message.replace_all_cs("%t", Anope::strftime(expire_at, founder));
226229

227-
Mail::Send(founder, subject, message);
230+
if (!Mail::Send(founder, subject, message))
231+
Log(LOG_DEBUG) << "m_expirenotice: failed to send channel expiring mail to " << founder->display;
228232
}
229233
if (successor && !successor->email.empty())
230234
{
231235
Anope::string message = base_message;
232236
message = message.replace_all_cs("%t", Anope::strftime(expire_at, successor));
233237

234-
Mail::Send(successor, subject, message);
238+
if (!Mail::Send(successor, subject, message))
239+
Log(LOG_DEBUG) << "m_expirenotice: failed to send channel expiring mail to " << successor->display;
235240
}
236241
}
237242
if (cs_notice_memo)
@@ -268,15 +273,21 @@ class ExpireNotice : public Module
268273

269274
if (cs_notice_mail)
270275
{
271-
Anope::string subject = Config->GetModule(this).Get<Anope::string>("cs_expired_subject"),
272-
message = Config->GetModule(this).Get<Anope::string>("cs_expired_message");
276+
Anope::string subject = Config->GetModule(this).Get<Anope::string>("cs_expired_subject", "Channel expired"),
277+
message = Config->GetModule(this).Get<Anope::string>("cs_expired_message", "Your channel %c has expired.\n%N IRC Administration");
273278
message = message.replace_all_cs("%c", ci->name);
274279
message = message.replace_all_cs("%N", networkname);
275280

276281
if (founder && !founder->email.empty())
277-
Mail::Send(founder, subject, message);
282+
{
283+
if (!Mail::Send(founder, subject, message))
284+
Log(LOG_DEBUG) << "m_expirenotice: failed to send channel expired mail to " << founder->display;
285+
}
278286
if (successor && !successor->email.empty())
279-
Mail::Send(successor, subject, message);
287+
{
288+
if (!Mail::Send(successor, subject, message))
289+
Log(LOG_DEBUG) << "m_expirenotice: failed to send channel expired mail to " << successor->display;
290+
}
280291
}
281292
if (cs_notice_memo)
282293
{
@@ -310,7 +321,8 @@ class ExpireNotice : public Module
310321
expiretimeout = Config->GetBlock("options").Get<time_t>("expiretimeout", "30m");
311322
networkname = Config->GetBlock("networkinfo").Get<Anope::string>("networkname");
312323

313-
if (!Config->GetBlock("mail").Get<bool>("usemail"))
324+
const auto &mail = Config->GetBlock("mail");
325+
if (!mail.Get<bool>("usemail") || mail.Get<const Anope::string>("sendfrom").empty())
314326
ns_notice_mail = cs_notice_mail = false;
315327
if (!MemoServ::service)
316328
ns_notice_memo = cs_notice_memo = false;

0 commit comments

Comments
 (0)