diff --git a/include/znc/Client.h b/include/znc/Client.h
index e68e3920..22f23e46 100644
--- a/include/znc/Client.h
+++ b/include/znc/Client.h
@@ -116,6 +116,7 @@ class CClient : public CIRCSocket {
bool HasCapNotify() const { return m_bCapNotify; }
bool HasAwayNotify() const { return m_bAwayNotify; }
bool HasAccountNotify() const { return m_bAccountNotify; }
+ bool HasInviteNotify() const { return m_bInviteNotify; }
bool HasExtendedJoin() const { return m_bExtendedJoin; }
bool HasNamesx() const { return m_bNamesx; }
bool HasUHNames() const { return m_bUHNames; }
@@ -180,8 +181,10 @@ class CClient : public CIRCSocket {
*
* Message type | Capability
* ------------ | ----------
- * \c ACCOUNT | \l CClient::HasAccountNotify() (account-notify)
- * \c AWAY | \l CClient::HasAwayNotify() (away-notify)
+ * \c ACCOUNT | \l CClient::HasAccountNotify() (account-notify)
+ * \c AWAY | \l CClient::HasAwayNotify() (away-notify)
+ * \c INVITE | \l CClient::HasInviteNotify() (invite-notify) if someone else is invited; invites sent to this user are not filtered out regardless of any capability.
+ * \c TAGMSG | \l CClient::HasMessageTagCap() (message-tags)
*
* ### Message tags
*
@@ -193,6 +196,7 @@ class CClient : public CIRCSocket {
* ----------- | ----------
* \c time | \l CClient::HasServerTime() (server-time)
* \c batch | \l CClient::HasBatch() (batch)
+ * any tag | \l CClient::HasMessageTagCap() (message-tags)
*
* Additional tags can be added via \l CClient::SetTagSupport().
*
@@ -321,6 +325,7 @@ class CClient : public CIRCSocket {
bool m_bCapNotify;
bool m_bAwayNotify;
bool m_bAccountNotify;
+ bool m_bInviteNotify;
bool m_bExtendedJoin;
bool m_bNamesx;
bool m_bUHNames;
diff --git a/include/znc/IRCSock.h b/include/znc/IRCSock.h
index 9a4ffefb..ccbd99c0 100644
--- a/include/znc/IRCSock.h
+++ b/include/znc/IRCSock.h
@@ -180,7 +180,7 @@ class CIRCSock : public CIRCSocket {
bool OnChgHostMessage(CChgHostMessage& Message);
bool OnCTCPMessage(CCTCPMessage& Message);
bool OnErrorMessage(CMessage& Message);
- bool OnInviteMessage(CMessage& Message);
+ bool OnInviteMessage(CInviteMessage& Message);
bool OnJoinMessage(CJoinMessage& Message);
bool OnKickMessage(CKickMessage& Message);
bool OnModeMessage(CModeMessage& Message);
diff --git a/include/znc/Message.h b/include/znc/Message.h
index f2686f00..970dc7a8 100644
--- a/include/znc/Message.h
+++ b/include/znc/Message.h
@@ -311,6 +311,15 @@ class CKickMessage : public CTargetMessage {
};
REGISTER_ZNC_MESSAGE(CKickMessage);
+class CInviteMessage : public CMessage {
+ public:
+ CString GetInvitedNick() const { return GetParam(0); }
+ void SetInvitedNick(const CString& sNick) { SetParam(0, sNick); }
+ CString GetChannel() const { return GetParam(1); }
+ void SetChannel(const CString& sChannel) { SetParam(1, sChannel); }
+};
+REGISTER_ZNC_MESSAGE(CInviteMessage);
+
class CPartMessage : public CTargetMessage {
public:
CString GetReason() const { return GetParam(1); }
diff --git a/include/znc/Modules.h b/include/znc/Modules.h
index 7a6f6769..ec8b36f8 100644
--- a/include/znc/Modules.h
+++ b/include/znc/Modules.h
@@ -751,11 +751,19 @@ class CModule {
virtual void OnPart(const CNick& Nick, CChan& Channel,
const CString& sMessage);
- /** Called when user is invited into a channel
+ /** Called when a user is invited to a channel.
+ * That includes the case of `invite-notify`.
+ * @since 1.10.0
+ * @param Message The message.
+ */
+ virtual EModRet OnInviteMessage(CInviteMessage& Message);
+ /** Called when user is invited into a channel.
+ * @note even in case of `invite-notify` this is only called for "you"
+ * being invited, as this function has no way to tell you whom is
+ * invited instead.
* @param Nick The nick who invited you.
* @param sChan The channel the user got invited into
* @return See CModule::EModRet.
- * @todo Add OnInviteMessage() hook
*/
virtual EModRet OnInvite(const CNick& Nick, const CString& sChan);
@@ -1610,6 +1618,7 @@ class CModules : public std::vector, private CCoreTranslationMixin {
bool OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage);
bool OnPartMessage(CPartMessage& Message);
bool OnInvite(const CNick& Nick, const CString& sChan);
+ bool OnInviteMessage(CInviteMessage& Message);
bool OnChanBufferStarting(CChan& Chan, CClient& Client);
bool OnChanBufferEnding(CChan& Chan, CClient& Client);
diff --git a/modules/modperl/functions.in b/modules/modperl/functions.in
index 7e03fdff..a44c818a 100644
--- a/modules/modperl/functions.in
+++ b/modules/modperl/functions.in
@@ -106,6 +106,7 @@ EModRet OnSendToIRCMessage(CMessage& Message)
EModRet OnUserTagMessage(CTargetMessage& Message)
EModRet OnChanTagMessage(CTargetMessage& Message)
EModRet OnPrivTagMessage(CTargetMessage& Message)
+EModRet OnInviteMessage(CInviteMessage& Message)
void OnClientGetSASLMechanisms(SCString& ssMechanisms)
EModRet OnClientSASLServerInitialChallenge(const CString& sMechanism, CString& sResponse)
diff --git a/modules/modperl/module.h b/modules/modperl/module.h
index a5ba56cb..92bae2d4 100644
--- a/modules/modperl/module.h
+++ b/modules/modperl/module.h
@@ -163,6 +163,7 @@ class ZNC_EXPORT_LIB_EXPORT CPerlModule : public CModule {
EModRet OnUserTagMessage(CTargetMessage& Message) override;
EModRet OnChanTagMessage(CTargetMessage& Message) override;
EModRet OnPrivTagMessage(CTargetMessage& Message) override;
+ EModRet OnInviteMessage(CInviteMessage& Message) override;
void OnClientGetSASLMechanisms(SCString& ssMechanisms) override;
EModRet OnClientSASLServerInitialChallenge(const CString& sMechanism,
diff --git a/modules/modperl/startup.pl b/modules/modperl/startup.pl
index 8834d436..e355127a 100644
--- a/modules/modperl/startup.pl
+++ b/modules/modperl/startup.pl
@@ -592,6 +592,7 @@ sub OnSendToIRCMessage {}
sub OnUserTagMessage {}
sub OnChanTagMessage {}
sub OnPrivTagMessage {}
+sub OnInviteMessage {}
# In Perl "undefined" is allowed value, so perl modules may continue using OnMode and not OnMode2
sub OnChanPermission2 { my $self = shift; $self->OnChanPermission(@_) }
diff --git a/modules/modpython/functions.in b/modules/modpython/functions.in
index 2ec2eee7..76ed437a 100644
--- a/modules/modpython/functions.in
+++ b/modules/modpython/functions.in
@@ -106,6 +106,7 @@ EModRet OnSendToIRCMessage(CMessage& Message)
EModRet OnUserTagMessage(CTargetMessage& Message)
EModRet OnChanTagMessage(CTargetMessage& Message)
EModRet OnPrivTagMessage(CTargetMessage& Message)
+EModRet OnInviteMessage(CInviteMessage& Message)
EModRet OnAddUser(CUser& User, CString& sErrorRet)
EModRet OnDeleteUser(CUser& User)
diff --git a/modules/modpython/module.h b/modules/modpython/module.h
index 295c170f..0f3d556e 100644
--- a/modules/modpython/module.h
+++ b/modules/modpython/module.h
@@ -183,6 +183,7 @@ class ZNC_EXPORT_LIB_EXPORT CPyModule : public CModule {
EModRet OnUserTagMessage(CTargetMessage& Message) override;
EModRet OnChanTagMessage(CTargetMessage& Message) override;
EModRet OnPrivTagMessage(CTargetMessage& Message) override;
+ EModRet OnInviteMessage(CInviteMessage& Message) override;
// Global Modules
EModRet OnAddUser(CUser& User, CString& sErrorRet) override;
diff --git a/modules/modpython/znc.py b/modules/modpython/znc.py
index 064dec6f..3665caa0 100644
--- a/modules/modpython/znc.py
+++ b/modules/modpython/znc.py
@@ -713,6 +713,9 @@ class Module:
def OnPrivTagMessage(self, msg):
pass
+ def OnInviteMessage(self, msg):
+ pass
+
class Command:
command = ''
diff --git a/src/Client.cpp b/src/Client.cpp
index f279d81c..48a8bac5 100644
--- a/src/Client.cpp
+++ b/src/Client.cpp
@@ -85,6 +85,7 @@ CClient::CClient()
m_bCapNotify(false),
m_bAwayNotify(false),
m_bAccountNotify(false),
+ m_bInviteNotify(false),
m_bExtendedJoin(false),
m_bNamesx(false),
m_bUHNames(false),
@@ -587,14 +588,25 @@ void CClient::PutClient(const CString& sLine) {
}
bool CClient::PutClient(const CMessage& Message) {
- if (!m_bAwayNotify && Message.GetType() == CMessage::Type::Away) {
- return false;
- } else if (!m_bAccountNotify &&
- Message.GetType() == CMessage::Type::Account) {
- return false;
- } else if (!m_bMessageTagCap &&
- Message.GetType() == CMessage::Type::TagMsg) {
- return false;
+ switch (Message.GetType()) {
+ case CMessage::Type::Away:
+ if (!m_bAwayNotify) return false;
+ break;
+ case CMessage::Type::Account:
+ if (!m_bAccountNotify) return false;
+ break;
+ case CMessage::Type::TagMsg:
+ if (!m_bMessageTagCap) return false;
+ break;
+ case CMessage::Type::Invite:
+ if (!m_bInviteNotify &&
+ !CNick(Message.As().GetInvitedNick())
+ .NickEquals(m_sNick)) {
+ return false;
+ }
+ break;
+ default:
+ break;
}
CMessage Msg(Message);
@@ -854,6 +866,10 @@ CClient::CoreCaps() {
[](CClient* pClient, bool bVal) {
pClient->m_bCapNotify = bVal;
}},
+ {"invite-notify",
+ [](CClient* pClient, bool bVal) {
+ pClient->m_bInviteNotify = bVal;
+ }},
{"chghost", [](CClient* pClient,
bool bVal) { pClient->m_bChgHost = bVal; }},
{"sasl",
diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp
index 1eea2b24..eec2faf2 100644
--- a/src/IRCSock.cpp
+++ b/src/IRCSock.cpp
@@ -386,6 +386,7 @@ bool CIRCSock::OnCapabilityMessage(CMessage& Message) {
{"multi-prefix", [this](bool bVal) { m_bNamesx = bVal; }},
{"userhost-in-names", [this](bool bVal) { m_bUHNames = bVal; }},
{"cap-notify", [](bool bVal) {}},
+ {"invite-notify", [](bool bVal) {}},
{"server-time", [this](bool bVal) { m_bServerTime = bVal; }},
{"znc.in/server-time-iso", [this](bool bVal) { m_bServerTime = bVal; }},
{"chghost", [](bool) {}},
@@ -596,10 +597,16 @@ bool CIRCSock::OnErrorMessage(CMessage& Message) {
return true;
}
-bool CIRCSock::OnInviteMessage(CMessage& Message) {
+bool CIRCSock::OnInviteMessage(CInviteMessage& Message) {
+ Message.SetChan(GetNetwork()->FindChan(Message.GetChannel()));
bool bResult = false;
- IRCSOCKMODULECALL(OnInvite(Message.GetNick(), Message.GetParam(1)),
- &bResult);
+ IRCSOCKMODULECALL(OnInviteMessage(Message), &bResult);
+ if (bResult) return true;
+ CNick InvitedNick = Message.GetInvitedNick();
+ if (InvitedNick.NickEquals(GetNick())) {
+ IRCSOCKMODULECALL(OnInvite(Message.GetNick(), Message.GetParam(1)),
+ &bResult);
+ }
return bResult;
}
diff --git a/src/Modules.cpp b/src/Modules.cpp
index 3b3246db..d553cbb7 100644
--- a/src/Modules.cpp
+++ b/src/Modules.cpp
@@ -890,6 +890,9 @@ CModule::EModRet CModule::OnPrivTagMessage(CTargetMessage& Message) {
CModule::EModRet CModule::OnChanTagMessage(CTargetMessage& Message) {
return CONTINUE;
}
+CModule::EModRet CModule::OnInviteMessage(CInviteMessage& Message) {
+ return CONTINUE;
+}
CModule::EModRet CModule::OnUserJoin(CString& sChannel, CString& sKey) {
return CONTINUE;
}
@@ -1448,6 +1451,9 @@ bool CModules::OnPrivTagMessage(CTargetMessage& Message) {
bool CModules::OnChanTagMessage(CTargetMessage& Message) {
MODHALTCHK(OnChanTagMessage(Message));
}
+bool CModules::OnInviteMessage(CInviteMessage& Message) {
+ MODHALTCHK(OnInviteMessage(Message));
+}
bool CModules::OnUserJoin(CString& sChannel, CString& sKey) {
MODHALTCHK(OnUserJoin(sChannel, sKey));
}
diff --git a/test/integration/tests/core.cpp b/test/integration/tests/core.cpp
index f1a0768e..c60707a9 100644
--- a/test/integration/tests/core.cpp
+++ b/test/integration/tests/core.cpp
@@ -1040,5 +1040,24 @@ TEST_F(ZNCTest, StatusAction) {
ASSERT_THAT(ircd.ReadRemainder().toStdString(), Not(HasSubstr("PRIVMSG")));
}
+TEST_F(ZNCTest, InviteNotify) {
+ auto znc = Run();
+ auto ircd = ConnectIRCd();
+ auto client = LoginClient();
+ client.Write("CAP REQ invite-notify");
+ client.ReadUntil("ACK");
+ auto client2 = LoginClient();
+
+ ircd.Write("001 nick Welcome");
+
+ ircd.Write(":source!id@ho INVITE nick #chan");
+ client.ReadUntil(":source!id@ho INVITE nick #chan");
+ client2.ReadUntil(":source!id@ho INVITE nick #chan");
+
+ ircd.Write(":source!id@ho INVITE someone #chan");
+ client.ReadUntil(":source!id@ho INVITE someone #chan");
+ ASSERT_THAT(client2.ReadRemainder().toStdString(), Not(HasSubstr("someone")));
+}
+
} // namespace
} // namespace znc_inttest