diff --git a/include/znc/Modules.h b/include/znc/Modules.h index 05b6bb6c..85c0d55a 100644 --- a/include/znc/Modules.h +++ b/include/znc/Modules.h @@ -1026,18 +1026,24 @@ class CModule { */ virtual EModRet OnDeleteNetwork(CIRCNetwork& Network); - /** Called when ZNC sends a raw traffic line to a client. - * @param sLine The raw traffic line sent. - * @param Client The client this line is sent to. + /** Called immediately before ZNC sends a raw traffic line to a client. + * @since 1.7.0 + * @param Message The message being sent to the client. * @warning Calling PutUser() from within this hook leads to infinite recursion. * @return See CModule::EModRet. */ + virtual EModRet OnSendToClientMessage(CMessage& Message); + /// @deprecated Use OnSendToClientMessage() instead. virtual EModRet OnSendToClient(CString& sLine, CClient& Client); - /** Called when ZNC sends a raw traffic line to the IRC server. - * @param sLine The raw traffic line sent. + + /** Called immediately before ZNC sends a raw traffic line to the IRC server. + * @since 1.7.0 + * @param Message The message being sent to the IRC server. * @warning Calling PutIRC() from within this hook leads to infinite recursion. * @return See CModule::EModRet. */ + virtual EModRet OnSendToIRCMessage(CMessage& Message); + /// @deprecated Use OnSendToIRCMessage() instead. virtual EModRet OnSendToIRC(CString& sLine); ModHandle GetDLL() { return m_pDLL; } @@ -1515,7 +1521,9 @@ class CModules : public std::vector { bool OnDeleteNetwork(CIRCNetwork& Network); bool OnSendToClient(CString& sLine, CClient& Client); + bool OnSendToClientMessage(CMessage& Message); bool OnSendToIRC(CString& sLine); + bool OnSendToIRCMessage(CMessage& Message); bool OnServerCapAvailable(const CString& sCap); bool OnServerCapResult(const CString& sCap, bool bSuccess); diff --git a/modules/modperl/functions.in b/modules/modperl/functions.in index b33299d2..9aa2e3a0 100644 --- a/modules/modperl/functions.in +++ b/modules/modperl/functions.in @@ -97,3 +97,5 @@ EModRet OnChanTextMessage(CTextMessage& Message) EModRet OnPrivNoticeMessage(CNoticeMessage& Message) EModRet OnChanNoticeMessage(CNoticeMessage& Message) EModRet OnTopicMessage(CTopicMessage& Message) +EModRet OnSendToClientMessage(CMessage& Message) +EModRet OnSendToIRCMessage(CMessage& Message) diff --git a/modules/modperl/module.h b/modules/modperl/module.h index f64102e9..cb2589ef 100644 --- a/modules/modperl/module.h +++ b/modules/modperl/module.h @@ -153,6 +153,8 @@ class ZNC_EXPORT_LIB_EXPORT CPerlModule : public CModule { EModRet OnPrivNoticeMessage(CNoticeMessage& Message) override; EModRet OnChanNoticeMessage(CNoticeMessage& Message) override; EModRet OnTopicMessage(CTopicMessage& Message) override; + EModRet OnSendToClientMessage(CMessage& Message) override; + EModRet OnSendToIRCMessage(CMessage& Message) override; }; static inline CPerlModule* AsPerlModule(CModule* p) { diff --git a/modules/modperl/startup.pl b/modules/modperl/startup.pl index 3b432740..545e8304 100644 --- a/modules/modperl/startup.pl +++ b/modules/modperl/startup.pl @@ -577,6 +577,8 @@ sub OnTopicMessage { $msg->SetTopic($topic); return $ret; } +sub OnSendToClientMessage {} +sub OnSendToIRCMessage {} # 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 12a7139a..49ed60cf 100644 --- a/modules/modpython/functions.in +++ b/modules/modpython/functions.in @@ -97,6 +97,8 @@ EModRet OnChanTextMessage(CTextMessage& Message) EModRet OnPrivNoticeMessage(CNoticeMessage& Message) EModRet OnChanNoticeMessage(CNoticeMessage& Message) EModRet OnTopicMessage(CTopicMessage& Message) +EModRet OnSendToClientMessage(CMessage& Message) +EModRet OnSendToIRCMessage(CMessage& Message) EModRet OnAddUser(CUser& User, CString& sErrorRet) EModRet OnDeleteUser(CUser& User) diff --git a/modules/modpython/module.h b/modules/modpython/module.h index 152aaec3..32a3e6dc 100644 --- a/modules/modpython/module.h +++ b/modules/modpython/module.h @@ -173,6 +173,8 @@ class ZNC_EXPORT_LIB_EXPORT CPyModule : public CModule { EModRet OnPrivNoticeMessage(CNoticeMessage& Message) override; EModRet OnChanNoticeMessage(CNoticeMessage& Message) override; EModRet OnTopicMessage(CTopicMessage& Message) override; + EModRet OnSendToClientMessage(CMessage& Message) override; + EModRet OnSendToIRCMessage(CMessage& Message) override; // Global Modules EModRet OnAddUser(CUser& User, CString& sErrorRet) override; diff --git a/modules/modpython/znc.py b/modules/modpython/znc.py index 42ad79eb..d2523e9b 100644 --- a/modules/modpython/znc.py +++ b/modules/modpython/znc.py @@ -655,6 +655,12 @@ class Module: def OnUnknownUserRawMessage(self, msg): pass + def OnSendToClientMessage(self, msg): + pass + + def OnSendToIRCMessage(self, msg): + pass + def make_inherit(cl, parent, attr): def make_caller(parent, name, attr): diff --git a/src/Client.cpp b/src/Client.cpp index 318ec7b5..db3f0456 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -483,13 +483,8 @@ CString CClient::GetFullName() const { } void CClient::PutClient(const CString& sLine) { - bool bReturn = false; - CString sCopy = sLine; - NETWORKMODULECALL(OnSendToClient(sCopy, *this), m_pUser, m_pNetwork, this, - &bReturn); - if (bReturn) return; - DEBUG("(" << GetFullName() << ") ZNC -> CLI [" << sCopy << "]"); - Write(sCopy + "\r\n"); + CMessage Message(sLine); + PutClient(Message); } bool CClient::PutClient(const CMessage& Message) { @@ -563,10 +558,7 @@ bool CClient::PutClient(const CMessage& Message) { } } - CString sLine = Msg.ToString(CMessage::ExcludeTags); - - // TODO: introduce a module hook that gives control over the tags that are - // sent + // TODO: add the ability to set a list of tags to send MCString mssTags; if (HasServerTime()) { @@ -585,11 +577,22 @@ bool CClient::PutClient(const CMessage& Message) { } } - if (!mssTags.empty()) { - CUtils::SetMessageTags(sLine, mssTags); - } + Msg.SetTags(mssTags); + Msg.SetClient(this); + Msg.SetNetwork(m_pNetwork); - PutClient(sLine); + bool bReturn = false; + NETWORKMODULECALL(OnSendToClientMessage(Msg), m_pUser, m_pNetwork, this, + &bReturn); + if (bReturn) return false; + + CString sCopy = Msg.ToString(); + NETWORKMODULECALL(OnSendToClient(sCopy, *this), m_pUser, m_pNetwork, this, + &bReturn); + if (bReturn) return false; + + DEBUG("(" << GetFullName() << ") ZNC -> CLI [" << sCopy << "]"); + Write(sCopy + "\r\n"); return true; } diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp index db10179f..8d2de76c 100644 --- a/src/IRCSock.cpp +++ b/src/IRCSock.cpp @@ -1154,13 +1154,20 @@ void CIRCSock::TrySend() { m_iSendsAllowed--; bool bSkip = false; CString& sLine = m_vsSendQueue.front(); - IRCSOCKMODULECALL(OnSendToIRC(sLine), &bSkip); + + CMessage Message(sLine); + Message.SetNetwork(m_pNetwork); + IRCSOCKMODULECALL(OnSendToIRCMessage(Message), &bSkip); + if (!bSkip) { - ; - DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/" - << m_pNetwork->GetName() << ") ZNC -> IRC [" << sLine - << "]"); - Write(sLine + "\r\n"); + CString sCopy = Message.ToString(); + IRCSOCKMODULECALL(OnSendToIRC(sCopy), &bSkip); + if (!bSkip) { + DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/" + << m_pNetwork->GetName() << ") ZNC -> IRC [" << sCopy + << "]"); + Write(sCopy + "\r\n"); + } } m_vsSendQueue.pop_front(); } diff --git a/src/Modules.cpp b/src/Modules.cpp index 99709b00..86b30c79 100644 --- a/src/Modules.cpp +++ b/src/Modules.cpp @@ -983,7 +983,14 @@ CModule::EModRet CModule::OnDeleteNetwork(CIRCNetwork& Network) { CModule::EModRet CModule::OnSendToClient(CString& sLine, CClient& Client) { return CONTINUE; } +CModule::EModRet CModule::OnSendToClientMessage(CMessage& Message) { + return CONTINUE; +} + CModule::EModRet CModule::OnSendToIRC(CString& sLine) { return CONTINUE; } +CModule::EModRet CModule::OnSendToIRCMessage(CMessage& Message) { + return CONTINUE; +} bool CModule::OnServerCapAvailable(const CString& sCap) { return false; } void CModule::OnServerCapResult(const CString& sCap, bool bSuccess) {} @@ -1444,7 +1451,13 @@ bool CModules::OnDeleteNetwork(CIRCNetwork& Network) { bool CModules::OnSendToClient(CString& sLine, CClient& Client) { MODHALTCHK(OnSendToClient(sLine, Client)); } +bool CModules::OnSendToClientMessage(CMessage& Message) { + MODHALTCHK(OnSendToClientMessage(Message)); +} bool CModules::OnSendToIRC(CString& sLine) { MODHALTCHK(OnSendToIRC(sLine)); } +bool CModules::OnSendToIRCMessage(CMessage& Message) { + MODHALTCHK(OnSendToIRCMessage(Message)); +} bool CModules::OnStatusCommand(CString& sCommand) { MODHALTCHK(OnStatusCommand(sCommand)); } diff --git a/test/ClientTest.cpp b/test/ClientTest.cpp index 9a5e40b5..193960f8 100644 --- a/test/ClientTest.cpp +++ b/test/ClientTest.cpp @@ -343,3 +343,23 @@ TEST_F(ClientTest, OnUserQuitMessage) { m_pTestClient->ReadLine(msg.ToString()); EXPECT_THAT(m_pTestSock->vsLines, IsEmpty()); // quit is never forwarded } + +TEST_F(ClientTest, OnSendToClientMessage) { + CMessage msg("PRIVMSG #chan :text"); + m_pTestModule->eAction = CModule::HALT; + m_pTestModule->bSendHooks = true; + m_pTestClient->PutClient(msg.ToString()); + + EXPECT_THAT(m_pTestModule->vsHooks, ElementsAre("OnSendToClientMessage")); + EXPECT_THAT(m_pTestModule->vsMessages, ElementsAre(msg.ToString())); + EXPECT_THAT(m_pTestModule->vNetworks, ElementsAre(m_pTestClient->GetNetwork())); + EXPECT_THAT(m_pTestModule->vClients, ElementsAre(m_pTestClient)); + EXPECT_THAT(m_pTestModule->vChannels, ElementsAre(nullptr)); + EXPECT_THAT(m_pTestSock->vsLines, IsEmpty()); // halt + + m_pTestModule->eAction = CModule::CONTINUE; + m_pTestClient->ReadLine(msg.ToString()); + + EXPECT_THAT(m_pTestSock->vsLines, ElementsAre(msg.ToString())); + m_pTestModule->bSendHooks = false; +} diff --git a/test/IRCSockTest.cpp b/test/IRCSockTest.cpp index 0029ec9f..a216e0fc 100644 --- a/test/IRCSockTest.cpp +++ b/test/IRCSockTest.cpp @@ -361,6 +361,25 @@ TEST_F(IRCSockTest, OnQuitMessage) { EXPECT_THAT(m_pTestClient->vsLines, ElementsAre(msg.ToString())); } +TEST_F(IRCSockTest, OnSendToIRCMessage) { + CMessage msg(":nick PRIVMSG #chan :hello"); + m_pTestModule->eAction = CModule::HALT; + m_pTestModule->bSendHooks = true; + m_pTestSock->PutIRC(msg.ToString()); + + EXPECT_THAT(m_pTestModule->vsHooks, ElementsAre("OnSendToIRCMessage")); + EXPECT_THAT(m_pTestModule->vsMessages, ElementsAre(msg.ToString())); + EXPECT_THAT(m_pTestModule->vNetworks, ElementsAre(m_pTestNetwork)); + EXPECT_THAT(m_pTestModule->vChannels, ElementsAre(nullptr)); + EXPECT_THAT(m_pTestClient->vsLines, IsEmpty()); // halt + + m_pTestModule->eAction = CModule::CONTINUE; + m_pTestSock->ReadLine(msg.ToString()); + + EXPECT_THAT(m_pTestClient->vsLines, ElementsAre(msg.ToString())); + m_pTestModule->bSendHooks = false; +} + TEST_F(IRCSockTest, OnTextMessage) { CMessage msg(":nick PRIVMSG #chan :hello"); m_pTestModule->eAction = CModule::HALT; diff --git a/test/IRCTest.h b/test/IRCTest.h index 5179e245..4fca41dc 100644 --- a/test/IRCTest.h +++ b/test/IRCTest.h @@ -129,6 +129,16 @@ class TestModule : public CModule { OnMessage(msg); } + EModRet OnSendToClientMessage(CMessage& msg) override { + if (!bSendHooks) return CONTINUE; + vsHooks.push_back("OnSendToClientMessage"); + return OnMessage(msg); + } + EModRet OnSendToIRCMessage(CMessage& msg) override { + if (!bSendHooks) return CONTINUE; + vsHooks.push_back("OnSendToIRCMessage"); + return OnMessage(msg); + } EModRet OnUserCTCPReplyMessage(CCTCPMessage& msg) override { vsHooks.push_back("OnUserCTCPReplyMessage"); return OnMessage(msg); @@ -188,6 +198,7 @@ class TestModule : public CModule { std::vector vClients; std::vector vChannels; EModRet eAction = CONTINUE; + bool bSendHooks = false; }; class IRCTest : public ::testing::Test {