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