From 51caa5c4cfba806e1312b29100374bd0de9c941d Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sun, 12 Jul 2015 00:16:57 +0200 Subject: [PATCH 1/8] Add CUtils::ParseServerTime() --- include/znc/Utils.h | 1 + src/Utils.cpp | 13 +++++++++++++ test/UtilsTest.cpp | 29 +++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/include/znc/Utils.h b/include/znc/Utils.h index 1ff5565b..41d809a4 100644 --- a/include/znc/Utils.h +++ b/include/znc/Utils.h @@ -83,6 +83,7 @@ public: static CString CTime(time_t t, const CString& sTZ); static CString FormatTime(time_t t, const CString& sFormat, const CString& sTZ); static CString FormatServerTime(const timeval& tv); + static timeval ParseServerTime(const CString& sTime); static SCString GetTimezones(); static SCString GetEncodings(); diff --git a/src/Utils.cpp b/src/Utils.cpp index 3dbd18c8..85f24d81 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -458,6 +458,19 @@ CString CUtils::FormatServerTime(const timeval& tv) { return CString(sTime) + "." + s_msec + "Z"; } +timeval CUtils::ParseServerTime(const CString& sTime) { + struct tm stm; + memset(&stm, 0, sizeof(stm)); + const char* cp = strptime(sTime.c_str(), "%Y-%m-%dT%H:%M:%S", &stm); + struct timeval tv; + tv.tv_sec = mktime(&stm); + CString s_usec(cp); + if (s_usec.TrimPrefix(".") && s_usec.TrimSuffix("Z")) { + tv.tv_usec = s_usec.ToULong() * 1000; + } + return tv; +} + namespace { void FillTimezones(const CString& sPath, SCString& result, const CString& sPrefix) { CDir Dir; diff --git a/test/UtilsTest.cpp b/test/UtilsTest.cpp index ea5bf21a..f3926ffb 100644 --- a/test/UtilsTest.cpp +++ b/test/UtilsTest.cpp @@ -75,3 +75,32 @@ TEST(IRC32, SetMessageTags) { EXPECT_EQ(R"(@a=\:\s\\\r\n :rest)", sLine); } +TEST(UtilsTest, ServerTime) { + char* oldTZ = getenv("TZ"); + if (oldTZ) oldTZ = strdup(oldTZ); + setenv("TZ", "UTC", 1); + tzset(); + + timeval tv1 = CUtils::ParseServerTime("2011-10-19T16:40:51.620Z"); + CString str1 = CUtils::FormatServerTime(tv1); + EXPECT_EQ("2011-10-19T16:40:51.620Z", str1); + + timeval now; + if (!gettimeofday(&now, nullptr)) { + now.tv_sec = time(nullptr); + now.tv_usec = 0; + } + + CString str2 = CUtils::FormatServerTime(now); + timeval tv2 = CUtils::ParseServerTime(str2); + EXPECT_EQ(now.tv_sec, tv2.tv_sec); + EXPECT_EQ(now.tv_usec, tv2.tv_usec); + + if (oldTZ) { + setenv("TZ", oldTZ, 1); + free(oldTZ); + } else { + unsetenv("TZ"); + } + tzset(); +} From a06bf1c9851c5ac69aa2d5d7d1560acf0e09b3f9 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 17 Feb 2015 23:46:33 +0100 Subject: [PATCH 2/8] Introduce a Message type (#506) --- Makefile.in | 4 +- configure.ac | 2 +- include/znc/Message.h | 90 +++++++++++++++++++++ src/Message.cpp | 177 ++++++++++++++++++++++++++++++++++++++++++ test/MessageTest.cpp | 139 +++++++++++++++++++++++++++++++++ 5 files changed, 409 insertions(+), 3 deletions(-) create mode 100644 include/znc/Message.h create mode 100644 src/Message.cpp create mode 100644 test/MessageTest.cpp diff --git a/Makefile.in b/Makefile.in index 1fc0fc67..5fefe0a6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -44,12 +44,12 @@ LIB_SRCS := ZNCString.cpp Csocket.cpp znc.cpp IRCNetwork.cpp User.cpp IRCSock.c Client.cpp Chan.cpp Nick.cpp Server.cpp Modules.cpp MD5.cpp Buffer.cpp Utils.cpp \ FileUtils.cpp HTTPSock.cpp Template.cpp ClientCommand.cpp Socket.cpp SHA256.cpp \ WebModules.cpp Listener.cpp Config.cpp ZNCDebug.cpp Threads.cpp version.cpp Query.cpp \ - SSLVerifyHost.cpp + SSLVerifyHost.cpp Message.cpp LIB_SRCS := $(addprefix src/,$(LIB_SRCS)) BIN_SRCS := src/main.cpp LIB_OBJS := $(patsubst %cpp,%o,$(LIB_SRCS)) BIN_OBJS := $(patsubst %cpp,%o,$(BIN_SRCS)) -TESTS := StringTest ConfigTest UtilsTest ThreadTest NickTest ClientTest NetworkTest +TESTS := StringTest ConfigTest UtilsTest ThreadTest NickTest ClientTest NetworkTest MessageTest TESTS := $(addprefix test/,$(addsuffix .o,$(TESTS))) CLEAN := znc src/*.o test/*.o core core.* .version_extra .depend modules/.depend \ unittest $(LIBZNC) diff --git a/configure.ac b/configure.ac index 86116074..34f0e7a1 100644 --- a/configure.ac +++ b/configure.ac @@ -292,7 +292,7 @@ if test "$POLL" = "yes"; then fi AC_CHECK_LIB( gnugetopt, getopt_long,) -AC_CHECK_FUNCS([lstat getopt_long getpassphrase]) +AC_CHECK_FUNCS([lstat getopt_long getpassphrase clock_gettime]) # ----- Check for dlopen diff --git a/include/znc/Message.h b/include/znc/Message.h new file mode 100644 index 00000000..b31a1c02 --- /dev/null +++ b/include/znc/Message.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2004-2015 ZNC, see the NOTICE file for details. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _MESSAGE_H +#define _MESSAGE_H + +#include +#include +#include +#include + +class CChan; +class CClient; +class CIRCNetwork; + +class CMessage { +public: + explicit CMessage(const CString& sMessage = ""); + CMessage(const CNick& Nick, const CString& sCommand, const VCString& vsParams, const MCString& mssTags = MCString::EmptyMap); + + // ZNC <-> IRC + CIRCNetwork* GetNetwork() const { return m_pNetwork; } + void SetNetwork(CIRCNetwork* pNetwork) { m_pNetwork = pNetwork; } + + // ZNC <-> CLI + CClient* GetClient() const { return m_pClient; } + void SetClient(CClient* pClient) { m_pClient = pClient; } + + CChan* GetChan() const { return m_pChan; } + void SetChan(CChan* pChan) { m_pChan = pChan; } + + CNick& GetNick() { return m_Nick; } + const CNick& GetNick() const { return m_Nick; } + void SetNick(const CNick& Nick) { m_Nick = Nick; } + + const CString& GetCommand() const { return m_sCommand; } + void SetCommand(const CString& sCommand) { m_sCommand = sCommand; } + + const VCString& GetParams() const { return m_vsParams; } + CString GetParams(unsigned int uIdx, unsigned int uLen = -1) const; + void SetParams(const VCString& vsParams) { m_vsParams = vsParams; } + + CString GetParam(unsigned int uIdx) const; + void SetParam(unsigned int uIdx, const CString& sParam); + + const timeval& GetTime() const { return m_time; } + void SetTime(const timeval& ts) { m_time = ts; } + + const MCString& GetTags() const { return m_mssTags; } + void SetTags(const MCString& mssTags) { m_mssTags = mssTags; } + + CString GetTag(const CString& sKey) const; + void SetTag(const CString& sKey, const CString& sValue); + + enum FormatFlags { + IncludeAll = 0x0, + ExcludePrefix = 0x1, + ExcludeTags = 0x2 + }; + + CString ToString(unsigned int uFlags = IncludeAll) const; + void Parse(CString sMessage); + +private: + void InitTime(); + + CNick m_Nick; + CString m_sCommand; + VCString m_vsParams; + MCString m_mssTags; + timeval m_time; + CIRCNetwork* m_pNetwork = nullptr; + CClient* m_pClient = nullptr; + CChan* m_pChan = nullptr; +}; + +#endif // !_MESSAGE_H diff --git a/src/Message.cpp b/src/Message.cpp new file mode 100644 index 00000000..d8615841 --- /dev/null +++ b/src/Message.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2004-2015 ZNC, see the NOTICE file for details. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +CMessage::CMessage(const CString& sMessage) +{ + Parse(sMessage); + InitTime(); +} + +CMessage::CMessage(const CNick& Nick, const CString& sCommand, const VCString& vsParams, const MCString& mssTags) + : m_Nick(Nick), m_sCommand(sCommand), m_vsParams(vsParams), m_mssTags(mssTags) +{ + InitTime(); +} + +CString CMessage::GetParams(unsigned int uIdx, unsigned int uLen) const +{ + VCString vsParams; + if (uLen > m_vsParams.size() - uIdx - 1) { + uLen = m_vsParams.size() - uIdx - 1; + } + for (unsigned int i = uIdx; i <= uIdx + uLen; ++i) { + CString sParam = m_vsParams[i]; + if (sParam.Contains(" ")) { + sParam = ":" + sParam; + } + vsParams.push_back(sParam); + } + return CString(" ").Join(vsParams.begin(), vsParams.end()); +} + +CString CMessage::GetParam(unsigned int uIdx) const +{ + if (uIdx >= m_vsParams.size()) { + return ""; + } + return m_vsParams[uIdx]; +} + +void CMessage::SetParam(unsigned int uIdx, const CString& sParam) +{ + if (uIdx >= m_vsParams.size()) { + m_vsParams.resize(uIdx + 1); + } + m_vsParams[uIdx] = sParam; +} + +CString CMessage::GetTag(const CString& sKey) const +{ + MCString::const_iterator it = m_mssTags.find(sKey); + if (it != m_mssTags.end()) { + return it->second; + } + return ""; +} + +void CMessage::SetTag(const CString& sKey, const CString& sValue) +{ + m_mssTags[sKey] = sValue; +} + +CString CMessage::ToString(unsigned int uFlags) const +{ + CString sMessage; + + // + if (!(uFlags & ExcludePrefix)) { + CString sPrefix = m_Nick.GetHostMask(); + if (!sPrefix.empty()) { + sMessage += ":" + sPrefix; + } + } + + // + if (!m_sCommand.empty()) { + if (!sMessage.empty()) { + sMessage += " "; + } + sMessage += m_sCommand; + } + + // + for (const CString& sParam : m_vsParams) { + sMessage += " "; + if (sParam.Contains(" ")) { + sMessage += ":"; + } + sMessage += sParam; + } + + // + if (!(uFlags & ExcludeTags)) { + CUtils::SetMessageTags(sMessage, m_mssTags); + } + + return sMessage; +} + +void CMessage::Parse(CString sMessage) +{ + // + if (sMessage.StartsWith("@")) { + m_mssTags = CUtils::GetMessageTags(sMessage); + sMessage = sMessage.Token(1, true); + } + + // ::= [':' ] + // ::= | [ '!' ] [ '@' ] + // ::= { } | + // ::= ' ' { ' ' } + // ::= [ ':' | ] + // ::= + // ::= + + // + if (sMessage.TrimLeft(":")) { + m_Nick.Parse(sMessage.Token(0)); + sMessage = sMessage.Token(1, true); + } + + // + m_sCommand = sMessage.Token(0); + sMessage = sMessage.Token(1, true); + + // + m_vsParams.clear(); + while (!sMessage.empty()) { + if (sMessage.TrimLeft(":")) { + m_vsParams.push_back(sMessage); + sMessage.clear(); + } else { + m_vsParams.push_back(sMessage.Token(0)); + sMessage = sMessage.Token(1, true); + } + } +} + +void CMessage::InitTime() +{ + auto it = m_mssTags.find("time"); + if (it != m_mssTags.end()) { + m_time = CUtils::ParseServerTime(it->second); + return; + } + +#ifdef HAVE_CLOCK_GETTIME + timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + m_time.tv_sec = ts.tv_sec; + m_time.tv_usec = ts.tv_nsec / 1000; + return; + } +#endif + + if (!gettimeofday(&m_time, nullptr)) { + m_time.tv_sec = time(nullptr); + m_time.tv_usec = 0; + } +} diff --git a/test/MessageTest.cpp b/test/MessageTest.cpp new file mode 100644 index 00000000..f9a73ec7 --- /dev/null +++ b/test/MessageTest.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2004-2015 ZNC, see the NOTICE file for details. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +TEST(MessageTest, SetParam) { + CMessage msg; + + msg.SetParam(1, "bar"); + msg.SetParam(0, "foo"); + + VCString params = {"foo", "bar"}; + EXPECT_EQ(params, msg.GetParams()); + + msg.SetParam(3, "baz"); + + params = {"foo", "bar", "", "baz"}; + EXPECT_EQ(params, msg.GetParams()); +} + +TEST(MessageTest, ToString) { + EXPECT_EQ("CMD", CMessage("CMD").ToString()); + EXPECT_EQ("CMD p1", CMessage("CMD p1").ToString()); + EXPECT_EQ("CMD p1 p2", CMessage("CMD p1 p2").ToString()); + EXPECT_EQ("CMD :p p p", CMessage("CMD :p p p").ToString()); + EXPECT_EQ(":irc.znc.in", CMessage(":irc.znc.in").ToString()); + EXPECT_EQ(":irc.znc.in CMD", CMessage(":irc.znc.in CMD").ToString()); + EXPECT_EQ(":irc.znc.in CMD p1", CMessage(":irc.znc.in CMD p1").ToString()); + EXPECT_EQ(":irc.znc.in CMD p1 p2", CMessage(":irc.znc.in CMD p1 p2").ToString()); + EXPECT_EQ(":irc.znc.in CMD :p p p", CMessage(":irc.znc.in CMD :p p p").ToString()); +} + +TEST(MessageTest, FormatFlags) { + const CString line = "@foo=bar :irc.example.com COMMAND param"; + + CMessage msg(line); + EXPECT_EQ(line, msg.ToString()); + EXPECT_EQ(":irc.example.com COMMAND param", msg.ToString(CMessage::ExcludeTags)); + EXPECT_EQ("@foo=bar COMMAND param", msg.ToString(CMessage::ExcludePrefix)); + EXPECT_EQ("COMMAND param", msg.ToString(CMessage::ExcludePrefix|CMessage::ExcludeTags)); +} + +// The test data for MessageTest.Parse originates from https://github.com/SaberUK/ircparser +// +// IRCParser - Internet Relay Chat Message Parser +// +// Copyright (C) 2015 Peter "SaberUK" Powell +// +// Permission to use, copy, modify, and/or distribute this software for any purpose with or without +// fee is hereby granted, provided that the above copyright notice and this permission notice appear +// in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +// OF THIS SOFTWARE. + +// when checking a valid message with tags and a source +TEST(MessageTest, ParseWithTags) { + const CString line = "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 :irc.example.com COMMAND param1 param2 :param3 param3"; + + MCString tags; + tags["tag1"] = "value1"; + tags["tag2"] = ""; + tags["vendor1/tag3"] = "value2"; + tags["vendor2/tag4"] = ""; + + VCString params = {"param1", "param2", "param3 param3"}; + + CMessage msg(line); + EXPECT_EQ(line, msg.ToString()); + EXPECT_EQ(tags, msg.GetTags()); + EXPECT_EQ("irc.example.com", msg.GetNick().GetNick()); + EXPECT_EQ("COMMAND", msg.GetCommand()); + EXPECT_EQ(params, msg.GetParams()); +} + +// when checking a valid message with a source but no tags +TEST(MessageTest, ParseWithoutTags) { + const CString line = ":irc.example.com COMMAND param1 param2 :param3 param3"; + + VCString params = {"param1", "param2", "param3 param3"}; + + CMessage msg(line); + EXPECT_EQ(line, msg.ToString()); + EXPECT_EQ(MCString(), msg.GetTags()); + EXPECT_EQ("irc.example.com", msg.GetNick().GetNick()); + EXPECT_EQ("COMMAND", msg.GetCommand()); + EXPECT_EQ(params, msg.GetParams()); +} + +// when checking a valid message with tags but no source +TEST(MessageTest, ParseWithoutSource) { + const CString line = "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 COMMAND param1 param2 :param3 param3"; + + MCString tags; + tags["tag1"] = "value1"; + tags["tag2"] = ""; + tags["vendor1/tag3"] = "value2"; + tags["vendor2/tag4"] = ""; + + VCString params = {"param1", "param2", "param3 param3"}; + + CMessage msg(line); + EXPECT_EQ(line, msg.ToString()); + EXPECT_EQ(tags, msg.GetTags()); + EXPECT_EQ("", msg.GetNick().GetNick()); + EXPECT_EQ("COMMAND", msg.GetCommand()); + EXPECT_EQ(params, msg.GetParams()); +} + +// when checking a valid message with no tags, source or parameters +TEST(MessageTest, ParseWithoutSourceAndTags) { + const CString line = "COMMAND"; + + CMessage msg(line); + EXPECT_EQ(line, msg.ToString()); + EXPECT_EQ(MCString(), msg.GetTags()); + EXPECT_EQ("", msg.GetNick().GetNick()); + EXPECT_EQ("COMMAND", msg.GetCommand()); + EXPECT_EQ(VCString(), msg.GetParams()); +} From 50ab0199012b2e82da7001128f99ced2d8312229 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 9 Jul 2015 00:50:57 +0200 Subject: [PATCH 3/8] CIRCSock::ReadLine(): use CMessage as a helper --- src/IRCSock.cpp | 108 +++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp index 15873910..c2a31914 100644 --- a/src/IRCSock.cpp +++ b/src/IRCSock.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include using std::set; @@ -156,29 +157,31 @@ void CIRCSock::ReadLine(const CString& sData) { IRCSOCKMODULECALL(OnRaw(sLine), &bReturn); if (bReturn) return; - if (sLine.StartsWith("PING ")) { + CMessage Message(sLine); + Message.SetNetwork(m_pNetwork); + + CString sCmd = Message.GetCommand(); + + if (sCmd.Equals("PING")) { // Generate a reply and don't forward this to any user, // we don't want any PING forwarded - PutIRCQuick("PONG " + sLine.substr(5)); + PutIRCQuick("PONG " + Message.GetParam(0)); return; - } else if (sLine.Token(1).Equals("PONG")) { + } else if (sCmd.Equals("PONG")) { // Block PONGs, we already responded to the pings return; - } else if (sLine.StartsWith("ERROR ")) { + } else if (sCmd.Equals("ERROR")) { //ERROR :Closing Link: nick[24.24.24.24] (Excess Flood) - CString sError(sLine.substr(6)); - sError.TrimPrefix(); + CString sError = Message.GetParam(0); m_pNetwork->PutStatus("Error from Server [" + sError + "]"); return; } - CString sCmd = sLine.Token(1); - if ((sCmd.length() == 3) && (isdigit(sCmd[0])) && (isdigit(sCmd[1])) && (isdigit(sCmd[2]))) { - CString sServer = sLine.Token(0).LeftChomp_n(); + CString sServer = Message.GetNick().GetHostMask(); unsigned int uRaw = sCmd.ToUInt(); - CString sNick = sLine.Token(2); - CString sRest = sLine.Token(3, true); + CString sNick = Message.GetParam(0); + CString sRest = Message.GetParams(1); CString sTmp; switch (uRaw) { @@ -224,9 +227,9 @@ void CIRCSock::ReadLine(const CString& sData) { m_pNetwork->UpdateExactRawBuffer(":" + _NAMEDFMT(sServer) + " " + sCmd + " {target} " + _NAMEDFMT(sRest)); break; case 10: { // :irc.server.com 010 nick : - CString sHost = sRest.Token(0); - CString sPort = sRest.Token(1); - CString sInfo = sRest.Token(2, true).TrimPrefix_n(); + CString sHost = Message.GetParam(1); + CString sPort = Message.GetParam(2); + CString sInfo = Message.GetParam(3); m_pNetwork->PutStatus("Server [" + m_pNetwork->GetCurrentServer()->GetString(false) + "] redirects us to [" + sHost + ":" + sPort + "] with reason [" + sInfo + "]"); m_pNetwork->PutStatus("Perhaps you want to add it as a new server."); @@ -278,7 +281,7 @@ void CIRCSock::ReadLine(const CString& sData) { CChan* pChan = m_pNetwork->FindChan(sRest.Token(0)); if (pChan) { - unsigned long ulDate = sLine.Token(4).ToULong(); + unsigned long ulDate = Message.GetParam(2).ToULong(); pChan->SetCreationDate(ulDate); if (!pChan->IsModeKnown()) { @@ -296,7 +299,7 @@ void CIRCSock::ReadLine(const CString& sData) { break; case 331: { // :irc.server.com 331 yournick #chan :No topic is set. - CChan* pChan = m_pNetwork->FindChan(sLine.Token(3)); + CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); if (pChan) { pChan->SetTopic(""); @@ -309,11 +312,10 @@ void CIRCSock::ReadLine(const CString& sData) { } case 332: { // :irc.server.com 332 yournick #chan :This is a topic - CChan* pChan = m_pNetwork->FindChan(sLine.Token(3)); + CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); if (pChan) { - CString sTopic = sLine.Token(4, true); - sTopic.LeftChomp(); + CString sTopic = Message.GetParam(2); pChan->SetTopic(sTopic); if (pChan->IsDetached()) { return; @@ -324,11 +326,11 @@ void CIRCSock::ReadLine(const CString& sData) { } case 333: { // :irc.server.com 333 yournick #chan setternick 1112320796 - CChan* pChan = m_pNetwork->FindChan(sLine.Token(3)); + CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); if (pChan) { - sNick = sLine.Token(4); - unsigned long ulDate = sLine.Token(5).ToULong(); + sNick = Message.GetParam(2); + unsigned long ulDate = Message.GetParam(3).ToULong(); pChan->SetTopicOwner(sNick); pChan->SetTopicDate(ulDate); @@ -342,13 +344,10 @@ void CIRCSock::ReadLine(const CString& sData) { } case 352: { // WHO // :irc.yourserver.com 352 yournick #chan ident theirhost.com irc.theirserver.com theirnick H :0 Real Name - sServer = sLine.Token(0); - sNick = sLine.Token(7); - CString sChan = sLine.Token(3); - CString sIdent = sLine.Token(4); - CString sHost = sLine.Token(5); - - sServer.LeftChomp(); + sNick = Message.GetParam(5); + CString sChan = Message.GetParam(1); + CString sIdent = Message.GetParam(2); + CString sHost = Message.GetParam(3); if (sNick.Equals(GetNick())) { m_Nick.SetIdent(sIdent); @@ -505,12 +504,10 @@ void CIRCSock::ReadLine(const CString& sData) { return; } } else { - CNick Nick(sLine.Token(0).TrimPrefix_n()); - sCmd = sLine.Token(1); - CString sRest = sLine.Token(2, true); + CNick Nick = Message.GetNick(); if (sCmd.Equals("NICK")) { - CString sNewNick = sRest.TrimPrefix_n(); + CString sNewNick = Message.GetParam(0); bool bIsVisible = false; vector vFoundChans; @@ -539,7 +536,7 @@ void CIRCSock::ReadLine(const CString& sData) { return; } } else if (sCmd.Equals("QUIT")) { - CString sMessage = sRest.TrimPrefix_n(); + CString sMessage = Message.GetParam(0); bool bIsVisible = false; // :nick!ident@host.com QUIT :message @@ -571,7 +568,7 @@ void CIRCSock::ReadLine(const CString& sData) { return; } } else if (sCmd.Equals("JOIN")) { - CString sChan = sRest.Token(0).TrimPrefix_n(); + CString sChan = Message.GetParam(0); CChan* pChan; if (Nick.NickEquals(GetNick())) { @@ -610,8 +607,8 @@ void CIRCSock::ReadLine(const CString& sData) { } } } else if (sCmd.Equals("PART")) { - CString sChan = sRest.Token(0).TrimPrefix_n(); - CString sMsg = sRest.Token(1, true).TrimPrefix_n(); + CString sChan = Message.GetParam(0); + CString sMsg = Message.GetParam(1); CChan* pChan = m_pNetwork->FindChan(sChan); bool bDetached = false; @@ -637,8 +634,8 @@ void CIRCSock::ReadLine(const CString& sData) { return; } } else if (sCmd.Equals("MODE")) { - CString sTarget = sRest.Token(0); - CString sModes = sRest.Token(1, true).TrimPrefix_n(":"); + CString sTarget = Message.GetParam(0); + CString sModes = Message.GetParams(1); CChan* pChan = m_pNetwork->FindChan(sTarget); if (pChan) { @@ -671,10 +668,9 @@ void CIRCSock::ReadLine(const CString& sData) { } } else if (sCmd.Equals("KICK")) { // :opnick!ident@host.com KICK #chan nick :msg - CString sChan = sRest.Token(0); - CString sKickedNick = sRest.Token(1); - CString sMsg = sRest.Token(2, true); - sMsg.LeftChomp(); + CString sChan = Message.GetParam(0); + CString sKickedNick = Message.GetParam(1); + CString sMsg = Message.GetParam(2); CChan* pChan = m_pNetwork->FindChan(sChan); @@ -697,9 +693,8 @@ void CIRCSock::ReadLine(const CString& sData) { } } else if (sCmd.Equals("NOTICE")) { // :nick!ident@host.com NOTICE #chan :Message - CString sTarget = sRest.Token(0); - CString sMsg = sRest.Token(1, true); - sMsg.LeftChomp(); + CString sTarget = Message.GetParam(0); + CString sMsg = Message.GetParam(1); if (sMsg.WildCmp("\001*\001")) { sMsg.LeftChomp(); @@ -734,11 +729,10 @@ void CIRCSock::ReadLine(const CString& sData) { return; } else if (sCmd.Equals("TOPIC")) { // :nick!ident@host.com TOPIC #chan :This is a topic - CChan* pChan = m_pNetwork->FindChan(sLine.Token(2)); + CChan* pChan = m_pNetwork->FindChan(Message.GetParam(0)); if (pChan) { - CString sTopic = sLine.Token(3, true); - sTopic.LeftChomp(); + CString sTopic = Message.GetParam(1); IRCSOCKMODULECALL(OnTopic(Nick, *pChan, sTopic), &bReturn); if (bReturn) return; @@ -755,8 +749,8 @@ void CIRCSock::ReadLine(const CString& sData) { } } else if (sCmd.Equals("PRIVMSG")) { // :nick!ident@host.com PRIVMSG #chan :Message - CString sTarget = sRest.Token(0); - CString sMsg = sRest.Token(1, true).TrimPrefix_n(); + CString sTarget = Message.GetParam(0); + CString sMsg = Message.GetParam(1); if (sMsg.WildCmp("\001*\001")) { sMsg.LeftChomp(); @@ -790,7 +784,7 @@ void CIRCSock::ReadLine(const CString& sData) { } } else if (sCmd.Equals("WALLOPS")) { // :blub!dummy@rox-8DBEFE92 WALLOPS :this is a test - CString sMsg = sRest.Token(0, true).TrimPrefix_n(); + CString sMsg = Message.GetParam(0); if (!m_pNetwork->IsUserOnline()) { m_pNetwork->AddNoticeBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " WALLOPS :{text}", sMsg); @@ -798,10 +792,10 @@ void CIRCSock::ReadLine(const CString& sData) { } else if (sCmd.Equals("CAP")) { // CAPs are supported only before authorization. if (!m_bAuthed) { - // sRest.Token(0) is most likely "*". No idea why, the + // The first parameter is most likely "*". No idea why, the // CAP spec don't mention this, but all implementations // I've seen add this extra asterisk - CString sSubCmd = sRest.Token(1); + CString sSubCmd = Message.GetParam(1); // If the caplist of a reply is too long, it's split // into multiple replies. A "*" is prepended to show @@ -815,10 +809,10 @@ void CIRCSock::ReadLine(const CString& sData) { // to recognize past request of NAK by 100 chars // of this reply. CString sArgs; - if (sRest.Token(2) == "*") { - sArgs = sRest.Token(3, true).TrimPrefix_n(); + if (Message.GetParam(2) == "*") { + sArgs = Message.GetParam(3); } else { - sArgs = sRest.Token(2, true).TrimPrefix_n(); + sArgs = Message.GetParam(2); } std::map> mSupportedCaps = { From ff181a4a85e31e265291b9d43e9493d12ea26f3b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 13 Jul 2015 20:33:48 +0200 Subject: [PATCH 4/8] Add specialized types and hooks for the most common msgs PRIVMSG, NOTICE, JOIN, PART, QUIT, NICK, KICK, TOPIC --- include/znc/IRCSock.h | 15 ++--- include/znc/Message.h | 103 ++++++++++++++++++++++++++++++ include/znc/Modules.h | 29 +++++++++ src/IRCSock.cpp | 133 ++++++++++++++++++++++----------------- src/Modules.cpp | 73 +++++++++++++++++++++ test/MessageTest.cpp | 143 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 431 insertions(+), 65 deletions(-) diff --git a/include/znc/IRCSock.h b/include/znc/IRCSock.h index a6949529..cf1fc0e9 100644 --- a/include/znc/IRCSock.h +++ b/include/znc/IRCSock.h @@ -28,6 +28,7 @@ class CChan; class CUser; class CIRCNetwork; class CClient; +class CMessage; // !Forward Declarations // TODO: This class needs new name @@ -49,13 +50,13 @@ public: // Message Handlers bool OnCTCPReply(CNick& Nick, CString& sMessage); - bool OnPrivCTCP(CNick& Nick, CString& sMessage); - bool OnChanCTCP(CNick& Nick, const CString& sChan, CString& sMessage); - bool OnGeneralCTCP(CNick& Nick, CString& sMessage); - bool OnPrivMsg(CNick& Nick, CString& sMessage); - bool OnChanMsg(CNick& Nick, const CString& sChan, CString& sMessage); - bool OnPrivNotice(CNick& Nick, CString& sMessage); - bool OnChanNotice(CNick& Nick, const CString& sChan, CString& sMessage); + bool OnPrivCTCP(CMessage& Message); + bool OnChanCTCP(CMessage& Message); + bool OnGeneralCTCP(CMessage& Message); + bool OnPrivMsg(CMessage& Message); + bool OnChanMsg(CMessage& Message); + bool OnPrivNotice(CMessage& Message); + bool OnChanNotice(CMessage& Message); bool OnServerCapAvailable(const CString& sCap); // !Message Handlers diff --git a/include/znc/Message.h b/include/znc/Message.h index b31a1c02..40a64d68 100644 --- a/include/znc/Message.h +++ b/include/znc/Message.h @@ -87,4 +87,107 @@ private: CChan* m_pChan = nullptr; }; +class CChanAction : public CMessage { +public: + CString GetText() const { return GetParam(1).TrimLeft_n("\001ACTION ").TrimRight_n("\001"); } + void SetText(const CString& sText) { SetParam(1, "\001ACTION " + sText + "\001"); } +}; + +class CChanCTCP : public CMessage { +public: + CString GetText() const { return GetParam(1).TrimLeft_n("\001").TrimRight_n("\001"); } + void SetText(const CString& sText) { SetParam(1, "\001" + sText + "\001"); } +}; + +class CChanMessage : public CMessage { +public: + CString GetText() const { return GetParam(1); } + void SetText(const CString& sText) { SetParam(1, sText); } +}; + +class CChanNotice : public CMessage { +public: + CString GetText() const { return GetParam(1); } + void SetText(const CString& sText) { SetParam(1, sText); } +}; + +class CJoinMessage : public CMessage { +public: +}; + +class CNickMessage : public CMessage { +public: + CString GetOldNick() const { return GetNick().GetNick(); } + CString GetNewNick() const { return GetParam(0); } + void SetNewNick(const CString& sNick) { SetParam(0, sNick); } +}; + +class CKickMessage : public CMessage { +public: + CString GetKickedNick() const { return GetParam(1); } + void SetKickedNick(const CString& sNick) { SetParam(1, sNick); } + CString GetReason() const { return GetParam(2); } + void SetReason(const CString& sReason) { SetParam(2, sReason); } +}; + +class CPartMessage : public CMessage { +public: + CString GetReason() const { return GetParam(1); } + void SetReason(const CString& sReason) { SetParam(1, sReason); } +}; + +class CPrivAction : public CMessage { +public: + CString GetText() const { return GetParam(1).TrimLeft_n("\001ACTION ").TrimRight_n("\001"); } + void SetText(const CString& sText) { SetParam(1, "\001ACTION " + sText + "\001"); } +}; + +class CPrivCTCP : public CMessage { +public: + CString GetText() const { return GetParam(1).TrimLeft_n("\001").TrimRight_n("\001"); } + void SetText(const CString& sText) { SetParam(1, "\001" + sText + "\001"); } +}; + +class CPrivMessage : public CMessage { +public: + CString GetText() const { return GetParam(1); } + void SetText(const CString& sText) { SetParam(1, sText); } +}; + +class CPrivNotice : public CMessage { +public: + CString GetText() const { return GetParam(1); } + void SetText(const CString& sText) { SetParam(1, sText); } +}; + +class CQuitMessage : public CMessage { +public: + CString GetReason() const { return GetParam(0); } + void SetReason(const CString& sReason) { SetParam(0, sReason); } +}; + +class CTopicMessage : public CMessage { +public: + CString GetTopic() const { return GetParam(1); } + void SetTopic(const CString& sTopic) { SetParam(1, sTopic); } +}; + +// The various CMessage subclasses are "mutable views" to the data held by CMessage. +// They provide convenient access to message type speficic attributes, but are not +// allowed to hold extra data of their own. +static_assert(sizeof(CChanAction) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CChanCTCP) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CChanMessage) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CChanNotice) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CJoinMessage) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CPartMessage) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CPrivAction) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CPrivCTCP) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CPrivMessage) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CPrivNotice) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CNickMessage) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CKickMessage) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CQuitMessage) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); +static_assert(sizeof(CTopicMessage) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); + #endif // !_MESSAGE_H diff --git a/include/znc/Modules.h b/include/znc/Modules.h index 5771c80d..fe5a2c75 100644 --- a/include/znc/Modules.h +++ b/include/znc/Modules.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -593,6 +594,7 @@ public: * @param vChans List of channels which you and nick share. */ virtual void OnQuit(const CNick& Nick, const CString& sMessage, const std::vector& vChans); + virtual void OnQuitMessage(CQuitMessage& Message, const std::vector& vChans); /** Called when a nickname change occurs. If we are changing our nick, * sNewNick will equal m_pIRCSock->GetNick(). * @param Nick The nick which changed its nickname @@ -600,6 +602,7 @@ public: * @param vChans Channels which we and nick share. */ virtual void OnNick(const CNick& Nick, const CString& sNewNick, const std::vector& vChans); + virtual void OnNickMessage(CNickMessage& Message, const std::vector& vChans); /** Called when a nick is kicked from a channel. * @param OpNick The nick which generated the kick. * @param sKickedNick The nick which was kicked. @@ -607,6 +610,7 @@ public: * @param sMessage The kick message. */ virtual void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage); + virtual void OnKickMessage(CKickMessage& Message); /** This module hook is called just before ZNC tries to join an IRC channel. * @param Chan The channel which is about to get joined. * @return See CModule::EModRet. @@ -617,12 +621,14 @@ public: * @param Channel The channel which was joined. */ virtual void OnJoin(const CNick& Nick, CChan& Channel); + virtual void OnJoinMessage(CJoinMessage& Message); /** Called when a nick parts a channel. * @param Nick The nick who parted. * @param Channel The channel which was parted. * @param sMessage The part message. */ virtual void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage); + virtual void OnPartMessage(CPartMessage& Message); /** Called when user is invited into a channel * @param Nick The nick who invited you. * @param sChan The channel the user got invited into @@ -749,6 +755,7 @@ public: * @return See CModule::EModRet. */ virtual EModRet OnPrivCTCP(CNick& Nick, CString& sMessage); + virtual EModRet OnPrivCTCPMessage(CPrivCTCP& Message); /** Called when we receive a channel CTCP request from IRC. * @param Nick The nick the CTCP request is from. * @param Channel The channel to which the request was sent. @@ -756,6 +763,7 @@ public: * @return See CModule::EModRet. */ virtual EModRet OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage); + virtual EModRet OnChanCTCPMessage(CChanCTCP& Message); /** Called when we receive a private CTCP ACTION ("/me" in query) from IRC. * This is called after CModule::OnPrivCTCP(). * @param Nick The nick the action came from. @@ -763,6 +771,7 @@ public: * @return See CModule::EModRet. */ virtual EModRet OnPrivAction(CNick& Nick, CString& sMessage); + virtual EModRet OnPrivActionMessage(CPrivAction& Message); /** Called when we receive a channel CTCP ACTION ("/me" in a channel) from IRC. * This is called after CModule::OnChanCTCP(). * @param Nick The nick the action came from. @@ -771,12 +780,14 @@ public: * @return See CModule::EModRet. */ virtual EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage); + virtual EModRet OnChanActionMessage(CChanAction& Message); /** Called when we receive a private message from IRC. * @param Nick The nick which sent the message. * @param sMessage The message. * @return See CModule::EModRet. */ virtual EModRet OnPrivMsg(CNick& Nick, CString& sMessage); + virtual EModRet OnPrivMessage(CPrivMessage& Message); /** Called when we receive a channel message from IRC. * @param Nick The nick which sent the message. * @param Channel The channel to which the message was sent. @@ -784,12 +795,14 @@ public: * @return See CModule::EModRet. */ virtual EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage); + virtual EModRet OnChanMessage(CChanMessage& Message); /** Called when we receive a private notice. * @param Nick The nick which sent the notice. * @param sMessage The notice message. * @return See CModule::EModRet. */ virtual EModRet OnPrivNotice(CNick& Nick, CString& sMessage); + virtual EModRet OnPrivNoticeMessage(CPrivNotice& Message); /** Called when we receive a channel notice. * @param Nick The nick which sent the notice. * @param Channel The channel to which the notice was sent. @@ -797,6 +810,7 @@ public: * @return See CModule::EModRet. */ virtual EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage); + virtual EModRet OnChanNoticeMessage(CChanNotice& Message); /** Called when we receive a channel topic change from IRC. * @param Nick The nick which changed the topic. * @param Channel The channel whose topic was changed. @@ -804,6 +818,7 @@ public: * @return See CModule::EModRet. */ virtual EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic); + virtual EModRet OnTopicMessage(CTopicMessage& Message); /** Called for every CAP received via CAP LS from server. * @param sCap capability supported by server. @@ -1186,11 +1201,16 @@ public: bool OnModCTCP(const CString& sMessage); bool OnQuit(const CNick& Nick, const CString& sMessage, const std::vector& vChans); + bool OnQuitMessage(CQuitMessage& Message, const std::vector& vChans); bool OnNick(const CNick& Nick, const CString& sNewNick, const std::vector& vChans); + bool OnNickMessage(CNickMessage& Message, const std::vector& vChans); bool OnKick(const CNick& Nick, const CString& sOpNick, CChan& Channel, const CString& sMessage); + bool OnKickMessage(CKickMessage& Message); bool OnJoining(CChan& Channel); bool OnJoin(const CNick& Nick, CChan& Channel); + bool OnJoinMessage(CJoinMessage& Message); bool OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage); + bool OnPartMessage(CPartMessage& Message); bool OnInvite(const CNick& Nick, const CString& sChan); bool OnChanBufferStarting(CChan& Chan, CClient& Client); @@ -1216,14 +1236,23 @@ public: bool OnCTCPReply(CNick& Nick, CString& sMessage); bool OnPrivCTCP(CNick& Nick, CString& sMessage); + bool OnPrivCTCPMessage(CPrivCTCP& Message); bool OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage); + bool OnChanCTCPMessage(CChanCTCP& Message); bool OnPrivAction(CNick& Nick, CString& sMessage); + bool OnPrivActionMessage(CPrivAction& Message); bool OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage); + bool OnChanActionMessage(CChanAction& Message); bool OnPrivMsg(CNick& Nick, CString& sMessage); + bool OnPrivMessage(CPrivMessage& Message); bool OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage); + bool OnChanMessage(CChanMessage& Message); bool OnPrivNotice(CNick& Nick, CString& sMessage); + bool OnPrivNoticeMessage(CPrivNotice& Message); bool OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage); + bool OnChanNoticeMessage(CChanNotice& Message); bool OnTopic(CNick& Nick, CChan& Channel, CString& sTopic); + bool OnTopicMessage(CTopicMessage& Message); bool OnTimerAutoJoin(CChan& Channel); bool OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet); diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp index c2a31914..5e650143 100644 --- a/src/IRCSock.cpp +++ b/src/IRCSock.cpp @@ -507,7 +507,8 @@ void CIRCSock::ReadLine(const CString& sData) { CNick Nick = Message.GetNick(); if (sCmd.Equals("NICK")) { - CString sNewNick = Message.GetParam(0); + CNickMessage& NickMsg = static_cast(Message); + CString sNewNick = NickMsg.GetNewNick(); bool bIsVisible = false; vector vFoundChans; @@ -530,19 +531,19 @@ void CIRCSock::ReadLine(const CString& sData) { m_pNetwork->PutUser(sLine); } - IRCSOCKMODULECALL(OnNick(Nick, sNewNick, vFoundChans), NOTHING); + IRCSOCKMODULECALL(OnNickMessage(NickMsg, vFoundChans), NOTHING); if (!bIsVisible) { return; } } else if (sCmd.Equals("QUIT")) { - CString sMessage = Message.GetParam(0); + CQuitMessage& QuitMsg = static_cast(Message); bool bIsVisible = false; // :nick!ident@host.com QUIT :message if (Nick.NickEquals(GetNick())) { - m_pNetwork->PutStatus("You quit [" + sMessage + "]"); + m_pNetwork->PutStatus("You quit [" + QuitMsg.GetReason() + "]"); // We don't call module hooks and we don't // forward this quit to clients (Some clients // disconnect if they receive such a QUIT) @@ -562,14 +563,15 @@ void CIRCSock::ReadLine(const CString& sData) { } } - IRCSOCKMODULECALL(OnQuit(Nick, sMessage, vFoundChans), NOTHING); + IRCSOCKMODULECALL(OnQuitMessage(QuitMsg, vFoundChans), NOTHING); if (!bIsVisible) { return; } } else if (sCmd.Equals("JOIN")) { - CString sChan = Message.GetParam(0); - CChan* pChan; + CJoinMessage& JoinMsg = static_cast(Message); + CString sChan = JoinMsg.GetParam(0); + CChan* pChan = nullptr; if (Nick.NickEquals(GetNick())) { m_pNetwork->AddChan(sChan, false); @@ -585,7 +587,8 @@ void CIRCSock::ReadLine(const CString& sData) { if (pChan) { pChan->AddNick(Nick.GetNickMask()); - IRCSOCKMODULECALL(OnJoin(Nick.GetNickMask(), *pChan), NOTHING); + JoinMsg.SetChan(pChan); + IRCSOCKMODULECALL(OnJoinMessage(JoinMsg), NOTHING); if (pChan->IsDetached()) { return; @@ -607,14 +610,15 @@ void CIRCSock::ReadLine(const CString& sData) { } } } else if (sCmd.Equals("PART")) { - CString sChan = Message.GetParam(0); - CString sMsg = Message.GetParam(1); + CPartMessage& PartMsg = static_cast(Message); + CString sChan = PartMsg.GetParam(0); CChan* pChan = m_pNetwork->FindChan(sChan); bool bDetached = false; if (pChan) { pChan->RemNick(Nick.GetNick()); - IRCSOCKMODULECALL(OnPart(Nick.GetNickMask(), *pChan, sMsg), NOTHING); + PartMsg.SetChan(pChan); + IRCSOCKMODULECALL(OnPartMessage(PartMsg), NOTHING); if (pChan->IsDetached()) bDetached = true; @@ -667,15 +671,16 @@ void CIRCSock::ReadLine(const CString& sData) { } } } else if (sCmd.Equals("KICK")) { + CKickMessage& KickMsg = static_cast(Message); // :opnick!ident@host.com KICK #chan nick :msg - CString sChan = Message.GetParam(0); - CString sKickedNick = Message.GetParam(1); - CString sMsg = Message.GetParam(2); + CString sChan = KickMsg.GetParam(0); + CString sKickedNick = KickMsg.GetKickedNick(); CChan* pChan = m_pNetwork->FindChan(sChan); if (pChan) { - IRCSOCKMODULECALL(OnKick(Nick, sKickedNick, *pChan, sMsg), NOTHING); + KickMsg.SetChan(pChan); + IRCSOCKMODULECALL(OnKickMessage(KickMsg), NOTHING); // do not remove the nick till after the OnKick call, so modules // can do Chan.FindNick or something to get more info. pChan->RemNick(sKickedNick); @@ -710,11 +715,11 @@ void CIRCSock::ReadLine(const CString& sData) { return; } else { if (sTarget.Equals(GetNick())) { - if (OnPrivNotice(Nick, sMsg)) { + if (OnPrivNotice(Message)) { return; } } else { - if (OnChanNotice(Nick, sTarget, sMsg)) { + if (OnChanNotice(Message)) { return; } } @@ -728,24 +733,24 @@ void CIRCSock::ReadLine(const CString& sData) { return; } else if (sCmd.Equals("TOPIC")) { + CTopicMessage& TopicMsg = static_cast(Message); // :nick!ident@host.com TOPIC #chan :This is a topic - CChan* pChan = m_pNetwork->FindChan(Message.GetParam(0)); + CChan* pChan = m_pNetwork->FindChan(TopicMsg.GetParam(0)); if (pChan) { - CString sTopic = Message.GetParam(1); - - IRCSOCKMODULECALL(OnTopic(Nick, *pChan, sTopic), &bReturn); + TopicMsg.SetChan(pChan); + IRCSOCKMODULECALL(OnTopicMessage(TopicMsg), &bReturn); if (bReturn) return; pChan->SetTopicOwner(Nick.GetNick()); pChan->SetTopicDate((unsigned long) time(nullptr)); - pChan->SetTopic(sTopic); + pChan->SetTopic(TopicMsg.GetTopic()); if (pChan->IsDetached()) { return; // Don't forward this } - sLine = ":" + Nick.GetNickMask() + " TOPIC " + pChan->GetName() + " :" + sTopic; + sLine = ":" + Nick.GetNickMask() + " TOPIC " + pChan->GetName() + " :" + pChan->GetTopic(); } } else if (sCmd.Equals("PRIVMSG")) { // :nick!ident@host.com PRIVMSG #chan :Message @@ -757,11 +762,11 @@ void CIRCSock::ReadLine(const CString& sData) { sMsg.RightChomp(); if (sTarget.Equals(GetNick())) { - if (OnPrivCTCP(Nick, sMsg)) { + if (OnPrivCTCP(Message)) { return; } } else { - if (OnChanCTCP(Nick, sTarget, sMsg)) { + if (OnChanCTCP(Message)) { return; } } @@ -770,11 +775,11 @@ void CIRCSock::ReadLine(const CString& sData) { return; } else { if (sTarget.Equals(GetNick())) { - if (OnPrivMsg(Nick, sMsg)) { + if (OnPrivMsg(Message)) { return; } } else { - if (OnChanMsg(Nick, sTarget, sMsg)) { + if (OnChanMsg(Message)) { return; } } @@ -911,31 +916,34 @@ bool CIRCSock::OnCTCPReply(CNick& Nick, CString& sMessage) { return bResult; } -bool CIRCSock::OnPrivCTCP(CNick& Nick, CString& sMessage) { +bool CIRCSock::OnPrivCTCP(CMessage& Message) { + CPrivCTCP& PrivCTCP = static_cast(Message); bool bResult = false; - IRCSOCKMODULECALL(OnPrivCTCP(Nick, sMessage), &bResult); + IRCSOCKMODULECALL(OnPrivCTCPMessage(PrivCTCP), &bResult); if (bResult) return true; - if (sMessage.TrimPrefix("ACTION ")) { + if (PrivCTCP.GetText().StartsWith("ACTION ")) { bResult = false; - IRCSOCKMODULECALL(OnPrivAction(Nick, sMessage), &bResult); + CPrivAction& PrivAction = static_cast(Message); + IRCSOCKMODULECALL(OnPrivActionMessage(PrivAction), &bResult); if (bResult) return true; if (!m_pNetwork->IsUserOnline() || !m_pNetwork->GetUser()->AutoClearQueryBuffer()) { + const CNick& Nick = PrivAction.GetNick(); CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); if (pQuery) { - pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :\001ACTION {text}\001", sMessage); + pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :\001ACTION {text}\001", PrivAction.GetText()); } } - - sMessage = "ACTION " + sMessage; } // This handles everything which wasn't handled yet - return OnGeneralCTCP(Nick, sMessage); + return OnGeneralCTCP(Message); } -bool CIRCSock::OnGeneralCTCP(CNick& Nick, CString& sMessage) { +bool CIRCSock::OnGeneralCTCP(CMessage& Message) { + const CNick& Nick = Message.GetNick(); + const CString& sMessage = Message.GetParam(1); const MCString& mssCTCPReplies = m_pNetwork->GetUser()->GetCTCPReplies(); CString sQuery = sMessage.Token(0).AsUpper(); MCString::const_iterator it = mssCTCPReplies.find(sQuery); @@ -979,83 +987,92 @@ bool CIRCSock::OnGeneralCTCP(CNick& Nick, CString& sMessage) { return false; } -bool CIRCSock::OnPrivNotice(CNick& Nick, CString& sMessage) { +bool CIRCSock::OnPrivNotice(CMessage& Message) { + CPrivNotice& PrivNotice = static_cast(Message); bool bResult = false; - IRCSOCKMODULECALL(OnPrivNotice(Nick, sMessage), &bResult); + IRCSOCKMODULECALL(OnPrivNoticeMessage(PrivNotice), &bResult); if (bResult) return true; if (!m_pNetwork->IsUserOnline()) { // If the user is detached, add to the buffer - m_pNetwork->AddNoticeBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " NOTICE {target} :{text}", sMessage); + m_pNetwork->AddNoticeBuffer(":" + _NAMEDFMT(PrivNotice.GetNick().GetNickMask()) + " NOTICE {target} :{text}", PrivNotice.GetText()); } return false; } -bool CIRCSock::OnPrivMsg(CNick& Nick, CString& sMessage) { +bool CIRCSock::OnPrivMsg(CMessage& Message) { + CPrivMessage& PrivMsg = static_cast(Message); bool bResult = false; - IRCSOCKMODULECALL(OnPrivMsg(Nick, sMessage), &bResult); + IRCSOCKMODULECALL(OnPrivMessage(PrivMsg), &bResult); if (bResult) return true; if (!m_pNetwork->IsUserOnline() || !m_pNetwork->GetUser()->AutoClearQueryBuffer()) { + const CNick& Nick = PrivMsg.GetNick(); CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); if (pQuery) { - pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :{text}", sMessage); + pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :{text}", PrivMsg.GetText()); } } return false; } -bool CIRCSock::OnChanCTCP(CNick& Nick, const CString& sChan, CString& sMessage) { - CChan* pChan = m_pNetwork->FindChan(sChan); +bool CIRCSock::OnChanCTCP(CMessage& Message) { + CChanCTCP& ChanCTCP = static_cast(Message); + CChan* pChan = m_pNetwork->FindChan(ChanCTCP.GetParam(0)); if (pChan) { bool bResult = false; - IRCSOCKMODULECALL(OnChanCTCP(Nick, *pChan, sMessage), &bResult); + ChanCTCP.SetChan(pChan); + IRCSOCKMODULECALL(OnChanCTCPMessage(ChanCTCP), &bResult); if (bResult) return true; // Record a /me - if (sMessage.TrimPrefix("ACTION ")) { + if (ChanCTCP.GetText().StartsWith("ACTION ")) { bResult = false; - IRCSOCKMODULECALL(OnChanAction(Nick, *pChan, sMessage), &bResult); + CChanAction& ChanAction = static_cast(Message); + IRCSOCKMODULECALL(OnChanActionMessage(ChanAction), &bResult); if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { - pChan->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sChan) + " :\001ACTION {text}\001", sMessage); + pChan->AddBuffer(":" + _NAMEDFMT(Message.GetNick().GetNickMask()) + " PRIVMSG " + _NAMEDFMT(pChan->GetName()) + " :\001ACTION {text}\001", ChanAction.GetText()); } - sMessage = "ACTION " + sMessage; } } - if (OnGeneralCTCP(Nick, sMessage)) + if (OnGeneralCTCP(Message)) return true; return (pChan && pChan->IsDetached()); } -bool CIRCSock::OnChanNotice(CNick& Nick, const CString& sChan, CString& sMessage) { - CChan* pChan = m_pNetwork->FindChan(sChan); +bool CIRCSock::OnChanNotice(CMessage& Message) { + CChanNotice& ChanNotice = static_cast(Message); + CChan* pChan = m_pNetwork->FindChan(ChanNotice.GetParam(0)); if (pChan) { bool bResult = false; - IRCSOCKMODULECALL(OnChanNotice(Nick, *pChan, sMessage), &bResult); + ChanNotice.SetChan(pChan); + IRCSOCKMODULECALL(OnChanNoticeMessage(ChanNotice), &bResult); if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { - pChan->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " NOTICE " + _NAMEDFMT(sChan) + " :{text}", sMessage); + pChan->AddBuffer(":" + _NAMEDFMT(ChanNotice.GetNick().GetNickMask()) + " NOTICE " + _NAMEDFMT(pChan->GetName()) + " :{text}", ChanNotice.GetText()); } } return ((pChan) && (pChan->IsDetached())); } -bool CIRCSock::OnChanMsg(CNick& Nick, const CString& sChan, CString& sMessage) { - CChan* pChan = m_pNetwork->FindChan(sChan); +bool CIRCSock::OnChanMsg(CMessage& Message) { + CChanMessage& ChanMsg = static_cast(Message); + CChan* pChan = m_pNetwork->FindChan(ChanMsg.GetParam(0)); if (pChan) { bool bResult = false; - IRCSOCKMODULECALL(OnChanMsg(Nick, *pChan, sMessage), &bResult); + ChanMsg.SetChan(pChan); + IRCSOCKMODULECALL(OnChanMessage(ChanMsg), &bResult); if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { - pChan->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sChan) + " :{text}", sMessage); + pChan->AddBuffer(":" + _NAMEDFMT(ChanMsg.GetNick().GetNickMask()) + " PRIVMSG " + _NAMEDFMT(pChan->GetName()) + " :{text}", ChanMsg.GetText()); } } diff --git a/src/Modules.cpp b/src/Modules.cpp index 2ce73a16..3b7f1578 100644 --- a/src/Modules.cpp +++ b/src/Modules.cpp @@ -636,11 +636,16 @@ void CModule::OnUnknownModCommand(const CString& sLine) { } void CModule::OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) {} +void CModule::OnQuitMessage(CQuitMessage& Message, const vector& vChans) { OnQuit(Message.GetNick(), Message.GetReason(), vChans); } void CModule::OnNick(const CNick& Nick, const CString& sNewNick, const vector& vChans) {} +void CModule::OnNickMessage(CNickMessage& Message, const vector& vChans) { OnNick(Message.GetNick(), Message.GetNewNick(), vChans); } void CModule::OnKick(const CNick& Nick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) {} +void CModule::OnKickMessage(CKickMessage& Message) { OnKick(Message.GetNick(), Message.GetKickedNick(), *Message.GetChan(), Message.GetReason()); } CModule::EModRet CModule::OnJoining(CChan& Channel) { return CONTINUE; } void CModule::OnJoin(const CNick& Nick, CChan& Channel) {} +void CModule::OnJoinMessage(CJoinMessage& Message) { OnJoin(Message.GetNick(), *Message.GetChan()); } void CModule::OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) {} +void CModule::OnPartMessage(CPartMessage& Message) { OnPart(Message.GetNick(), *Message.GetChan(), Message.GetReason()); } CModule::EModRet CModule::OnInvite(const CNick& Nick, const CString& sChan) { return CONTINUE; } CModule::EModRet CModule::OnChanBufferStarting(CChan& Chan, CClient& Client) { return CONTINUE; } @@ -671,14 +676,68 @@ CModule::EModRet CModule::OnUserQuit(CString& sMessage) { return CONTINUE; } CModule::EModRet CModule::OnCTCPReply(CNick& Nick, CString& sMessage) { return CONTINUE; } CModule::EModRet CModule::OnPrivCTCP(CNick& Nick, CString& sMessage) { return CONTINUE; } +CModule::EModRet CModule::OnPrivCTCPMessage(CPrivCTCP& Message) { + CString sText = Message.GetText(); + EModRet ret = OnPrivCTCP(Message.GetNick(), sText); + Message.SetText(sText); + return ret; +} CModule::EModRet CModule::OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) { return CONTINUE; } +CModule::EModRet CModule::OnChanCTCPMessage(CChanCTCP& Message) { + CString sText = Message.GetText(); + EModRet ret = OnChanCTCP(Message.GetNick(), *Message.GetChan(), sText); + Message.SetText(sText); + return ret; +} CModule::EModRet CModule::OnPrivAction(CNick& Nick, CString& sMessage) { return CONTINUE; } +CModule::EModRet CModule::OnPrivActionMessage(CPrivAction& Message) { + CString sText = Message.GetText(); + EModRet ret = OnPrivAction(Message.GetNick(), sText); + Message.SetText(sText); + return ret; +} CModule::EModRet CModule::OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) { return CONTINUE; } +CModule::EModRet CModule::OnChanActionMessage(CChanAction& Message) { + CString sText = Message.GetText(); + EModRet ret = OnChanAction(Message.GetNick(), *Message.GetChan(), sText); + Message.SetText(sText); + return ret; +} CModule::EModRet CModule::OnPrivMsg(CNick& Nick, CString& sMessage) { return CONTINUE; } +CModule::EModRet CModule::OnPrivMessage(CPrivMessage& Message) { + CString sText = Message.GetText(); + EModRet ret = OnPrivMsg(Message.GetNick(), sText); + Message.SetText(sText); + return ret; +} CModule::EModRet CModule::OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) { return CONTINUE; } +CModule::EModRet CModule::OnChanMessage(CChanMessage& Message) { + CString sText = Message.GetText(); + EModRet ret = OnChanMsg(Message.GetNick(), *Message.GetChan(), sText); + Message.SetText(sText); + return ret; +} CModule::EModRet CModule::OnPrivNotice(CNick& Nick, CString& sMessage) { return CONTINUE; } +CModule::EModRet CModule::OnPrivNoticeMessage(CPrivNotice& Message) { + CString sText = Message.GetText(); + EModRet ret = OnPrivNotice(Message.GetNick(), sText); + Message.SetText(sText); + return ret; +} CModule::EModRet CModule::OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) { return CONTINUE; } +CModule::EModRet CModule::OnChanNoticeMessage(CChanNotice& Message) { + CString sText = Message.GetText(); + EModRet ret = OnChanNotice(Message.GetNick(), *Message.GetChan(), sText); + Message.SetText(sText); + return ret; +} CModule::EModRet CModule::OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) { return CONTINUE; } +CModule::EModRet CModule::OnTopicMessage(CTopicMessage& Message) { + CString sTopic = Message.GetTopic(); + EModRet ret = OnTopic(Message.GetNick(), *Message.GetChan(), sTopic); + Message.SetTopic(sTopic); + return ret; +} CModule::EModRet CModule::OnTimerAutoJoin(CChan& Channel) { return CONTINUE; } CModule::EModRet CModule::OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) { return CONTINUE; } CModule::EModRet CModule::OnDeleteNetwork(CIRCNetwork& Network) { return CONTINUE; } @@ -829,11 +888,16 @@ bool CModules::OnUserTopicRequest(CString& sChannel) { MODHALTCHK(OnUserTopicReq bool CModules::OnUserQuit(CString& sMessage) { MODHALTCHK(OnUserQuit(sMessage)); } bool CModules::OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) { MODUNLOADCHK(OnQuit(Nick, sMessage, vChans)); return false; } +bool CModules::OnQuitMessage(CQuitMessage& Message, const vector& vChans) { MODUNLOADCHK(OnQuitMessage(Message, vChans)); return false; } bool CModules::OnNick(const CNick& Nick, const CString& sNewNick, const vector& vChans) { MODUNLOADCHK(OnNick(Nick, sNewNick, vChans)); return false; } +bool CModules::OnNickMessage(CNickMessage& Message, const vector& vChans) { MODUNLOADCHK(OnNickMessage(Message, vChans)); return false; } bool CModules::OnKick(const CNick& Nick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) { MODUNLOADCHK(OnKick(Nick, sKickedNick, Channel, sMessage)); return false; } +bool CModules::OnKickMessage(CKickMessage& Message) { MODUNLOADCHK(OnKickMessage(Message)); return false; } bool CModules::OnJoining(CChan& Channel) { MODHALTCHK(OnJoining(Channel)); } bool CModules::OnJoin(const CNick& Nick, CChan& Channel) { MODUNLOADCHK(OnJoin(Nick, Channel)); return false; } +bool CModules::OnJoinMessage(CJoinMessage& Message) { MODUNLOADCHK(OnJoinMessage(Message)); return false; } bool CModules::OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) { MODUNLOADCHK(OnPart(Nick, Channel, sMessage)); return false; } +bool CModules::OnPartMessage(CPartMessage& Message) { MODUNLOADCHK(OnPartMessage(Message)); return false; } bool CModules::OnInvite(const CNick& Nick, const CString& sChan) { MODHALTCHK(OnInvite(Nick, sChan)); } bool CModules::OnChanBufferStarting(CChan& Chan, CClient& Client) { MODHALTCHK(OnChanBufferStarting(Chan, Client)); } bool CModules::OnChanBufferEnding(CChan& Chan, CClient& Client) { MODHALTCHK(OnChanBufferEnding(Chan, Client)); } @@ -843,14 +907,23 @@ bool CModules::OnPrivBufferPlayLine2(CClient& Client, CString& sLine, const time bool CModules::OnPrivBufferPlayLine(CClient& Client, CString& sLine) { MODHALTCHK(OnPrivBufferPlayLine(Client, sLine)); } bool CModules::OnCTCPReply(CNick& Nick, CString& sMessage) { MODHALTCHK(OnCTCPReply(Nick, sMessage)); } bool CModules::OnPrivCTCP(CNick& Nick, CString& sMessage) { MODHALTCHK(OnPrivCTCP(Nick, sMessage)); } +bool CModules::OnPrivCTCPMessage(CPrivCTCP& Message) { MODHALTCHK(OnPrivCTCPMessage(Message)); } bool CModules::OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) { MODHALTCHK(OnChanCTCP(Nick, Channel, sMessage)); } +bool CModules::OnChanCTCPMessage(CChanCTCP& Message) { MODHALTCHK(OnChanCTCPMessage(Message)); } bool CModules::OnPrivAction(CNick& Nick, CString& sMessage) { MODHALTCHK(OnPrivAction(Nick, sMessage)); } +bool CModules::OnPrivActionMessage(CPrivAction& Message) { MODHALTCHK(OnPrivActionMessage(Message)); } bool CModules::OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) { MODHALTCHK(OnChanAction(Nick, Channel, sMessage)); } +bool CModules::OnChanActionMessage(CChanAction& Message) { MODHALTCHK(OnChanActionMessage(Message)); } bool CModules::OnPrivMsg(CNick& Nick, CString& sMessage) { MODHALTCHK(OnPrivMsg(Nick, sMessage)); } +bool CModules::OnPrivMessage(CPrivMessage& Message) { MODHALTCHK(OnPrivMessage(Message)); } bool CModules::OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) { MODHALTCHK(OnChanMsg(Nick, Channel, sMessage)); } +bool CModules::OnChanMessage(CChanMessage& Message) { MODHALTCHK(OnChanMessage(Message)); } bool CModules::OnPrivNotice(CNick& Nick, CString& sMessage) { MODHALTCHK(OnPrivNotice(Nick, sMessage)); } +bool CModules::OnPrivNoticeMessage(CPrivNotice& Message) { MODHALTCHK(OnPrivNoticeMessage(Message)); } bool CModules::OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) { MODHALTCHK(OnChanNotice(Nick, Channel, sMessage)); } +bool CModules::OnChanNoticeMessage(CChanNotice& Message) { MODHALTCHK(OnChanNoticeMessage(Message)); } bool CModules::OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) { MODHALTCHK(OnTopic(Nick, Channel, sTopic)); } +bool CModules::OnTopicMessage(CTopicMessage& Message) { MODHALTCHK(OnTopicMessage(Message)); } bool CModules::OnTimerAutoJoin(CChan& Channel) { MODHALTCHK(OnTimerAutoJoin(Channel)); } bool CModules::OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) { MODHALTCHK(OnAddNetwork(Network, sErrorRet)); } bool CModules::OnDeleteNetwork(CIRCNetwork& Network) { MODHALTCHK(OnDeleteNetwork(Network)); } diff --git a/test/MessageTest.cpp b/test/MessageTest.cpp index f9a73ec7..77d56a69 100644 --- a/test/MessageTest.cpp +++ b/test/MessageTest.cpp @@ -55,6 +55,149 @@ TEST(MessageTest, FormatFlags) { EXPECT_EQ("COMMAND param", msg.ToString(CMessage::ExcludePrefix|CMessage::ExcludeTags)); } +TEST(MessageTest, ChanAction) { + CMessage msg(":sender PRIVMSG #chan :\001ACTION text\001"); + CChanAction& chan = static_cast(msg); + EXPECT_EQ("sender", chan.GetNick().GetNick()); + EXPECT_EQ("PRIVMSG", chan.GetCommand()); + EXPECT_EQ("text", chan.GetText()); + + chan.SetText("foo bar"); + EXPECT_EQ("foo bar", chan.GetText()); + EXPECT_EQ(":sender PRIVMSG #chan :\001ACTION foo bar\001", chan.ToString()); +} + +TEST(MessageTest, ChanCTCP) { + CMessage msg(":sender PRIVMSG #chan :\001text\001"); + CChanCTCP& chan = static_cast(msg); + EXPECT_EQ("sender", chan.GetNick().GetNick()); + EXPECT_EQ("PRIVMSG", chan.GetCommand()); + EXPECT_EQ("text", chan.GetText()); + + chan.SetText("foo bar"); + EXPECT_EQ("foo bar", chan.GetText()); + EXPECT_EQ(":sender PRIVMSG #chan :\001foo bar\001", chan.ToString()); +} + +TEST(MessageTest, ChanMsg) { + CMessage msg(":sender PRIVMSG #chan :text"); + CChanMessage& priv = static_cast(msg); + EXPECT_EQ("sender", priv.GetNick().GetNick()); + EXPECT_EQ("PRIVMSG", priv.GetCommand()); + EXPECT_EQ("text", priv.GetText()); + + priv.SetText("foo bar"); + EXPECT_EQ("foo bar", priv.GetText()); + EXPECT_EQ(":sender PRIVMSG #chan :foo bar", priv.ToString()); +} + +TEST(MessageTest, Kick) { + CMessage msg(":nick KICK #chan person :reason"); + CKickMessage& kick = static_cast(msg); + EXPECT_EQ("nick", kick.GetNick().GetNick()); + EXPECT_EQ("KICK", kick.GetCommand()); + EXPECT_EQ("person", kick.GetKickedNick()); + EXPECT_EQ("reason", kick.GetReason()); + + kick.SetKickedNick("noone"); + EXPECT_EQ("noone", kick.GetKickedNick()); + kick.SetReason("test"); + EXPECT_EQ("test", kick.GetReason()); + EXPECT_EQ(":nick KICK #chan noone test", kick.ToString()); +} + +TEST(MessageTest, Join) { + CMessage msg(":nick JOIN #chan"); + EXPECT_EQ("nick", msg.GetNick().GetNick()); + EXPECT_EQ("JOIN", msg.GetCommand()); + EXPECT_EQ("#chan", msg.GetParam(0)); +} + +TEST(MessageTest, Nick) { + CMessage msg(":nick NICK person"); + CNickMessage& nick = static_cast(msg); + EXPECT_EQ("nick", nick.GetNick().GetNick()); + EXPECT_EQ("NICK", nick.GetCommand()); + EXPECT_EQ("nick", nick.GetOldNick()); + EXPECT_EQ("person", nick.GetNewNick()); + + nick.SetNewNick("test"); + EXPECT_EQ("test", nick.GetNewNick()); + EXPECT_EQ(":nick NICK test", nick.ToString()); +} + +TEST(MessageTest, Part) { + CMessage msg(":nick PART #chan :reason"); + CPartMessage& part = static_cast(msg); + EXPECT_EQ("nick", part.GetNick().GetNick()); + EXPECT_EQ("PART", part.GetCommand()); + EXPECT_EQ("reason", part.GetReason()); + + part.SetReason("test"); + EXPECT_EQ("test", part.GetReason()); + EXPECT_EQ(":nick PART #chan test", part.ToString()); +} + +TEST(MessageTest, PrivAction) { + CMessage msg(":sender PRIVMSG receiver :\001ACTION text\001"); + CPrivAction& priv = static_cast(msg); + EXPECT_EQ("sender", priv.GetNick().GetNick()); + EXPECT_EQ("PRIVMSG", priv.GetCommand()); + EXPECT_EQ("text", priv.GetText()); + + priv.SetText("foo bar"); + EXPECT_EQ("foo bar", priv.GetText()); + EXPECT_EQ(":sender PRIVMSG receiver :\001ACTION foo bar\001", priv.ToString()); +} + +TEST(MessageTest, PrivCTCP) { + CMessage msg(":sender PRIVMSG receiver :\001text\001"); + CPrivCTCP& priv = static_cast(msg); + EXPECT_EQ("sender", priv.GetNick().GetNick()); + EXPECT_EQ("PRIVMSG", priv.GetCommand()); + EXPECT_EQ("text", priv.GetText()); + + priv.SetText("foo bar"); + EXPECT_EQ("foo bar", priv.GetText()); + EXPECT_EQ(":sender PRIVMSG receiver :\001foo bar\001", priv.ToString()); +} + +TEST(MessageTest, PrivMsg) { + CMessage msg(":sender PRIVMSG receiver :text"); + CPrivMessage& priv = static_cast(msg); + EXPECT_EQ("sender", priv.GetNick().GetNick()); + EXPECT_EQ("PRIVMSG", priv.GetCommand()); + EXPECT_EQ("text", priv.GetText()); + + priv.SetText("foo bar"); + EXPECT_EQ("foo bar", priv.GetText()); + EXPECT_EQ(":sender PRIVMSG receiver :foo bar", priv.ToString()); +} + +TEST(MessageTest, Quit) { + CMessage msg(":nick QUIT :reason"); + CQuitMessage& quit = static_cast(msg); + EXPECT_EQ("nick", quit.GetNick().GetNick()); + EXPECT_EQ("QUIT", quit.GetCommand()); + EXPECT_EQ("reason", quit.GetReason()); + + quit.SetReason("test"); + EXPECT_EQ("test", quit.GetReason()); + EXPECT_EQ(":nick QUIT test", quit.ToString()); +} + +TEST(MessageTest, Topic) { + CMessage msg(":nick TOPIC #chan :topic"); + CTopicMessage& topic = static_cast(msg); + EXPECT_EQ("nick", topic.GetNick().GetNick()); + EXPECT_EQ("TOPIC", topic.GetCommand()); + EXPECT_EQ("topic", topic.GetTopic()); + + topic.SetTopic("test"); + EXPECT_EQ("test", topic.GetTopic()); + EXPECT_EQ(":nick TOPIC #chan test", topic.ToString()); +} + // The test data for MessageTest.Parse originates from https://github.com/SaberUK/ircparser // // IRCParser - Internet Relay Chat Message Parser From c17c8c022b7aa21bf2e689828b1fd0e46347c713 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 11 Jul 2015 20:49:43 +0200 Subject: [PATCH 5/8] Buffer message tags and the original timestamps --- include/znc/Buffer.h | 7 +++++-- include/znc/Chan.h | 2 +- include/znc/Query.h | 2 +- modules/buffextras.cpp | 44 ++++++++++++++++++++++++++++-------------- src/Buffer.cpp | 6 +++--- src/Client.cpp | 13 +++++++------ src/IRCSock.cpp | 10 +++++----- 7 files changed, 52 insertions(+), 32 deletions(-) diff --git a/include/znc/Buffer.h b/include/znc/Buffer.h index 1314b028..17f33be1 100644 --- a/include/znc/Buffer.h +++ b/include/znc/Buffer.h @@ -29,7 +29,7 @@ class CClient; class CBufLine { public: CBufLine() : CBufLine("") { throw 0; } // shouldn't be called, but is needed for compilation - CBufLine(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr); + CBufLine(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr, const MCString& mssTags = MCString::EmptyMap); ~CBufLine(); CString GetLine(const CClient& Client, const MCString& msParams) const; void UpdateTime(); @@ -38,12 +38,14 @@ public: void SetFormat(const CString& sFormat) { m_sFormat = sFormat; } void SetText(const CString& sText) { m_sText = sText; } void SetTime(const timeval& ts) { m_time = ts; } + void SetTags(const MCString& mssTags) { m_mssTags = mssTags; } // !Setters // Getters const CString& GetFormat() const { return m_sFormat; } const CString& GetText() const { return m_sText; } timeval GetTime() const { return m_time; } + const MCString& GetTags() const { return m_mssTags; } // !Getters private: @@ -51,6 +53,7 @@ protected: CString m_sFormat; CString m_sText; timeval m_time; + MCString m_mssTags; }; class CBuffer : private std::deque { @@ -58,7 +61,7 @@ public: CBuffer(unsigned int uLineCount = 100); ~CBuffer(); - size_type AddLine(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr); + size_type AddLine(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr, const MCString& mssTags = MCString::EmptyMap); /// Same as AddLine, but replaces a line whose format string starts with sMatch if there is one. size_type UpdateLine(const CString& sMatch, const CString& sFormat, const CString& sText = ""); /// Same as UpdateLine, but does nothing if this exact line already exists. diff --git a/include/znc/Chan.h b/include/znc/Chan.h index 29fd12f1..033c5af0 100644 --- a/include/znc/Chan.h +++ b/include/znc/Chan.h @@ -98,7 +98,7 @@ public: bool SetBufferCount(unsigned int u, bool bForce = false) { m_bHasBufferCountSet = true; return m_Buffer.SetLineCount(u, bForce); } void InheritBufferCount(unsigned int u, bool bForce = false) { if (!m_bHasBufferCountSet) m_Buffer.SetLineCount(u, bForce); } void ResetBufferCount(); - size_t AddBuffer(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr) { return m_Buffer.AddLine(sFormat, sText, ts); } + size_t AddBuffer(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr, const MCString& mssTags = MCString::EmptyMap) { return m_Buffer.AddLine(sFormat, sText, ts, mssTags); } void ClearBuffer() { m_Buffer.Clear(); } void SendBuffer(CClient* pClient); void SendBuffer(CClient* pClient, const CBuffer& Buffer); diff --git a/include/znc/Query.h b/include/znc/Query.h index 8627f9bd..8612cd88 100644 --- a/include/znc/Query.h +++ b/include/znc/Query.h @@ -38,7 +38,7 @@ public: const CBuffer& GetBuffer() const { return m_Buffer; } unsigned int GetBufferCount() const { return m_Buffer.GetLineCount(); } bool SetBufferCount(unsigned int u, bool bForce = false) { return m_Buffer.SetLineCount(u, bForce); } - size_t AddBuffer(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr) { return m_Buffer.AddLine(sFormat, sText, ts); } + size_t AddBuffer(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr, const MCString& mssTags = MCString::EmptyMap) { return m_Buffer.AddLine(sFormat, sText, ts, mssTags); } void ClearBuffer() { m_Buffer.Clear(); } void SendBuffer(CClient* pClient); void SendBuffer(CClient* pClient, const CBuffer& Buffer); diff --git a/modules/buffextras.cpp b/modules/buffextras.cpp index 5f395f1b..9c6ec5b3 100644 --- a/modules/buffextras.cpp +++ b/modules/buffextras.cpp @@ -25,12 +25,12 @@ public: virtual ~CBuffExtras() {} - void AddBuffer(CChan& Channel, const CString& sMessage) { + void AddBuffer(CChan& Channel, const CString& sMessage, const timeval* tv = nullptr, const MCString& mssTags = MCString::EmptyMap) { // If they have AutoClearChanBuffer enabled, only add messages if no client is connected if (Channel.AutoClearChanBuffer() && GetNetwork()->IsUserOnline()) return; - Channel.AddBuffer(":" + GetModNick() + "!" + GetModName() + "@znc.in PRIVMSG " + _NAMEDFMT(Channel.GetName()) + " :{text}", sMessage); + Channel.AddBuffer(":" + GetModNick() + "!" + GetModName() + "@znc.in PRIVMSG " + _NAMEDFMT(Channel.GetName()) + " :{text}", sMessage, tv, mssTags); } void OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs) override { @@ -38,34 +38,50 @@ public: AddBuffer(Channel, sNickMask + " set mode: " + sModes + " " + sArgs); } - void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) override { - AddBuffer(Channel, OpNick.GetNickMask() + " kicked " + sKickedNick + " Reason: [" + sMessage + "]"); + void OnKickMessage(CKickMessage& Message) override { + const CNick& OpNick = Message.GetNick(); + const CString sKickedNick = Message.GetKickedNick(); + CChan& Channel = *Message.GetChan(); + const CString sMessage = Message.GetReason(); + AddBuffer(Channel, OpNick.GetNickMask() + " kicked " + sKickedNick + " Reason: [" + sMessage + "]", &Message.GetTime(), Message.GetTags()); } - void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) override { + void OnQuitMessage(CQuitMessage& Message, const vector& vChans) override { + const CNick& Nick = Message.GetNick(); + const CString sMessage = Message.GetReason(); CString sMsg = Nick.GetNickMask() + " quit with message: [" + sMessage + "]"; for (CChan* pChan : vChans) { - AddBuffer(*pChan, sMsg); + AddBuffer(*pChan, sMsg, &Message.GetTime(), Message.GetTags()); } } - void OnJoin(const CNick& Nick, CChan& Channel) override { - AddBuffer(Channel, Nick.GetNickMask() + " joined"); + void OnJoinMessage(CJoinMessage& Message) override { + const CNick& Nick = Message.GetNick(); + CChan& Channel = *Message.GetChan(); + AddBuffer(Channel, Nick.GetNickMask() + " joined", &Message.GetTime(), Message.GetTags()); } - void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) override { - AddBuffer(Channel, Nick.GetNickMask() + " parted with message: [" + sMessage + "]"); + void OnPartMessage(CPartMessage& Message) override { + const CNick& Nick = Message.GetNick(); + CChan& Channel = *Message.GetChan(); + const CString sMessage = Message.GetReason(); + AddBuffer(Channel, Nick.GetNickMask() + " parted with message: [" + sMessage + "]", &Message.GetTime(), Message.GetTags()); } - void OnNick(const CNick& OldNick, const CString& sNewNick, const vector& vChans) override { + void OnNickMessage(CNickMessage& Message, const vector& vChans) override { + const CNick& OldNick = Message.GetNick(); + const CString sNewNick = Message.GetNewNick(); CString sMsg = OldNick.GetNickMask() + " is now known as " + sNewNick; for (CChan* pChan : vChans) { - AddBuffer(*pChan, sMsg); + AddBuffer(*pChan, sMsg, &Message.GetTime(), Message.GetTags()); } } - EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) override { - AddBuffer(Channel, Nick.GetNickMask() + " changed the topic to: " + sTopic); + EModRet OnTopicMessage(CTopicMessage& Message) override { + const CNick& Nick = Message.GetNick(); + CChan& Channel = *Message.GetChan(); + const CString sTopic = Message.GetTopic(); + AddBuffer(Channel, Nick.GetNickMask() + " changed the topic to: " + sTopic, &Message.GetTime(), Message.GetTags()); return CONTINUE; } diff --git a/src/Buffer.cpp b/src/Buffer.cpp index dc14642c..f0102fea 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -19,7 +19,7 @@ #include #include -CBufLine::CBufLine(const CString& sFormat, const CString& sText, const timeval* ts) : m_sFormat(sFormat), m_sText(sText), m_time() { +CBufLine::CBufLine(const CString& sFormat, const CString& sText, const timeval* ts, const MCString& mssTags) : m_sFormat(sFormat), m_sText(sText), m_time(), m_mssTags(mssTags) { if (ts == nullptr) UpdateTime(); else @@ -54,7 +54,7 @@ CBuffer::CBuffer(unsigned int uLineCount) : m_uLineCount(uLineCount) { CBuffer::~CBuffer() {} -CBuffer::size_type CBuffer::AddLine(const CString& sFormat, const CString& sText, const timeval* ts) { +CBuffer::size_type CBuffer::AddLine(const CString& sFormat, const CString& sText, const timeval* ts, const MCString& mssTags) { if (!m_uLineCount) { return 0; } @@ -63,7 +63,7 @@ CBuffer::size_type CBuffer::AddLine(const CString& sFormat, const CString& sText erase(begin()); } - push_back(CBufLine(sFormat, sText, ts)); + push_back(CBufLine(sFormat, sText, ts, mssTags)); return size(); } diff --git a/src/Client.cpp b/src/Client.cpp index 1e87b191..d2f898dd 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -96,8 +96,9 @@ void CClient::ReadLine(const CString& sData) { DEBUG("(" << GetFullName() << ") CLI -> ZNC [" << sLine << "]"); + MCString mssTags; if (sLine.StartsWith("@")) { - // TODO support message-tags properly + mssTags = CUtils::GetMessageTags(sLine); sLine = sLine.Token(1, true); } @@ -262,7 +263,7 @@ void CClient::ReadLine(const CString& sData) { CChan* pChan = m_pNetwork->FindChan(sTarget); if ((pChan) && (!pChan->AutoClearChanBuffer())) { - pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " NOTICE " + _NAMEDFMT(sTarget) + " :{text}", sMsg); + pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " NOTICE " + _NAMEDFMT(sTarget) + " :{text}", sMsg, nullptr, mssTags); } // Relay to the rest of the clients that may be connected to this user @@ -313,13 +314,13 @@ void CClient::ReadLine(const CString& sData) { CChan* pChan = m_pNetwork->FindChan(sTarget); if (pChan && (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline())) { - pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :\001ACTION {text}\001", sMessage); + pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :\001ACTION {text}\001", sMessage, nullptr, mssTags); } } else { if (!m_pUser->AutoClearQueryBuffer() || !m_pNetwork->IsUserOnline()) { CQuery* pQuery = m_pNetwork->AddQuery(sTarget); if (pQuery) { - pQuery->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :\001ACTION {text}\001", sMessage); + pQuery->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :\001ACTION {text}\001", sMessage, nullptr, mssTags); } } } @@ -368,13 +369,13 @@ void CClient::ReadLine(const CString& sData) { CChan* pChan = m_pNetwork->FindChan(sTarget); if ((pChan) && (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline())) { - pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :{text}", sMsg); + pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :{text}", sMsg, nullptr, mssTags); } } else { if (!m_pUser->AutoClearQueryBuffer() || !m_pNetwork->IsUserOnline()) { CQuery* pQuery = m_pNetwork->AddQuery(sTarget); if (pQuery) { - pQuery->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :{text}", sMsg); + pQuery->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :{text}", sMsg, nullptr, mssTags); } } } diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp index 5e650143..422e5c04 100644 --- a/src/IRCSock.cpp +++ b/src/IRCSock.cpp @@ -932,7 +932,7 @@ bool CIRCSock::OnPrivCTCP(CMessage& Message) { const CNick& Nick = PrivAction.GetNick(); CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); if (pQuery) { - pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :\001ACTION {text}\001", PrivAction.GetText()); + pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :\001ACTION {text}\001", PrivAction.GetText(), &PrivAction.GetTime(), PrivAction.GetTags()); } } } @@ -1011,7 +1011,7 @@ bool CIRCSock::OnPrivMsg(CMessage& Message) { const CNick& Nick = PrivMsg.GetNick(); CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); if (pQuery) { - pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :{text}", PrivMsg.GetText()); + pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :{text}", PrivMsg.GetText(), &PrivMsg.GetTime(), PrivMsg.GetTags()); } } @@ -1034,7 +1034,7 @@ bool CIRCSock::OnChanCTCP(CMessage& Message) { IRCSOCKMODULECALL(OnChanActionMessage(ChanAction), &bResult); if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { - pChan->AddBuffer(":" + _NAMEDFMT(Message.GetNick().GetNickMask()) + " PRIVMSG " + _NAMEDFMT(pChan->GetName()) + " :\001ACTION {text}\001", ChanAction.GetText()); + pChan->AddBuffer(":" + _NAMEDFMT(Message.GetNick().GetNickMask()) + " PRIVMSG " + _NAMEDFMT(pChan->GetName()) + " :\001ACTION {text}\001", ChanAction.GetText(), &ChanAction.GetTime(), ChanAction.GetTags()); } } } @@ -1055,7 +1055,7 @@ bool CIRCSock::OnChanNotice(CMessage& Message) { if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { - pChan->AddBuffer(":" + _NAMEDFMT(ChanNotice.GetNick().GetNickMask()) + " NOTICE " + _NAMEDFMT(pChan->GetName()) + " :{text}", ChanNotice.GetText()); + pChan->AddBuffer(":" + _NAMEDFMT(ChanNotice.GetNick().GetNickMask()) + " NOTICE " + _NAMEDFMT(pChan->GetName()) + " :{text}", ChanNotice.GetText(), &ChanNotice.GetTime(), ChanNotice.GetTags()); } } @@ -1072,7 +1072,7 @@ bool CIRCSock::OnChanMsg(CMessage& Message) { if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { - pChan->AddBuffer(":" + _NAMEDFMT(ChanMsg.GetNick().GetNickMask()) + " PRIVMSG " + _NAMEDFMT(pChan->GetName()) + " :{text}", ChanMsg.GetText()); + pChan->AddBuffer(":" + _NAMEDFMT(ChanMsg.GetNick().GetNickMask()) + " PRIVMSG " + _NAMEDFMT(pChan->GetName()) + " :{text}", ChanMsg.GetText(), &ChanMsg.GetTime(), ChanMsg.GetTags()); } } From 8a7c79bb78a37d38e7425e378d6353bf1687da88 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sun, 12 Jul 2015 11:37:27 +0200 Subject: [PATCH 6/8] Pass known/compatible tags to clients --- include/znc/Client.h | 2 ++ include/znc/IRCNetwork.h | 2 ++ include/znc/IRCSock.h | 1 + src/Client.cpp | 30 ++++++++++++++++++++++++++++++ src/IRCNetwork.cpp | 14 ++++++++++++++ src/IRCSock.cpp | 9 ++++----- 6 files changed, 53 insertions(+), 5 deletions(-) diff --git a/include/znc/Client.h b/include/znc/Client.h index f9ae87c2..2acba558 100644 --- a/include/znc/Client.h +++ b/include/znc/Client.h @@ -29,6 +29,7 @@ class CUser; class CIRCNetwork; class CIRCSock; class CClient; +class CMessage; // !Forward Declarations class CAuthBase { @@ -175,6 +176,7 @@ public: void PutIRC(const CString& sLine); void PutClient(const CString& sLine); + void PutClient(const CMessage& Message); unsigned int PutStatus(const CTable& table); void PutStatus(const CString& sLine); void PutStatusNotice(const CString& sLine); diff --git a/include/znc/IRCNetwork.h b/include/znc/IRCNetwork.h index d5016af4..3b7130c2 100644 --- a/include/znc/IRCNetwork.h +++ b/include/znc/IRCNetwork.h @@ -35,6 +35,7 @@ class CServer; class CIRCSock; class CIRCNetworkPingTimer; class CIRCNetworkJoinTimer; +class CMessage; class CIRCNetwork { public: @@ -90,6 +91,7 @@ public: // !Modules bool PutUser(const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); + bool PutUser(const CMessage& Message, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutStatus(const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutModule(const CString& sModule, const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); diff --git a/include/znc/IRCSock.h b/include/znc/IRCSock.h index cf1fc0e9..0d7caa60 100644 --- a/include/znc/IRCSock.h +++ b/include/znc/IRCSock.h @@ -114,6 +114,7 @@ public: CString GetISupport(const CString& sKey, const CString& sDefault = "") const; // !Getters + // TODO: CMessage // This handles NAMESX and UHNAMES in a raw 353 reply void ForwardRaw353(const CString& sLine) const; void ForwardRaw353(const CString& sLine, CClient* pClient) const; diff --git a/src/Client.cpp b/src/Client.cpp index d2f898dd..9ab2cc50 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -794,6 +794,36 @@ void CClient::PutClient(const CString& sLine) { Write(sCopy + "\r\n"); } +void CClient::PutClient(const CMessage& Message) +{ + CString sLine = Message.ToString(CMessage::ExcludeTags); + + // TODO: introduce a module hook that gives control over the tags that are sent + MCString mssTags; + + if (HasServerTime()) { + CString sServerTime = Message.GetTag("time"); + if (!sServerTime.empty()) { + mssTags["time"] = sServerTime; + } else { + mssTags["time"] = CUtils::FormatServerTime(Message.GetTime()); + } + } + + if (HasBatch()) { + CString sBatch = Message.GetTag("batch"); + if (!sBatch.empty()) { + mssTags["batch"] = sBatch; + } + } + + if (!mssTags.empty()) { + CUtils::SetMessageTags(sLine, mssTags); + } + + PutClient(sLine); +} + void CClient::PutStatusNotice(const CString& sLine) { PutModNotice("status", sLine); } diff --git a/src/IRCNetwork.cpp b/src/IRCNetwork.cpp index c5ed38c8..452b7fd5 100644 --- a/src/IRCNetwork.cpp +++ b/src/IRCNetwork.cpp @@ -726,6 +726,20 @@ bool CIRCNetwork::PutUser(const CString& sLine, CClient* pClient, CClient* pSkip return (pClient == nullptr); } +bool CIRCNetwork::PutUser(const CMessage& Message, CClient* pClient, CClient* pSkipClient) { + for (CClient* pEachClient : m_vClients) { + if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) { + pEachClient->PutClient(Message); + + if (pClient) { + return true; + } + } + } + + return (pClient == nullptr); +} + bool CIRCNetwork::PutStatus(const CString& sLine, CClient* pClient, CClient* pSkipClient) { for (CClient* pEachClient : m_vClients) { if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) { diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp index 422e5c04..77e0c391 100644 --- a/src/IRCSock.cpp +++ b/src/IRCSock.cpp @@ -369,7 +369,7 @@ void CIRCSock::ReadLine(const CString& sData) { const vector& vClients = m_pNetwork->GetClients(); for (CClient* pClient : vClients) { if (pClient->HasNamesx()) { - m_pNetwork->PutUser(sLine, pClient); + m_pNetwork->PutUser(Message, pClient); } else { // The client doesn't support multi-prefix so we need to remove // the other prefixes. @@ -379,6 +379,7 @@ void CIRCSock::ReadLine(const CString& sData) { if (pos >= 2 && pos != CString::npos) { sNewNick = sNick[0] + sNick.substr(pos); } + // TODO: CMessage CString sNewLine = sServer + " 352 " + sLine.Token(2) + " " + sChan + " " + sIdent + " " + sHost + " " + sLine.Token(6) + " " + sNewNick + " " + @@ -528,7 +529,7 @@ void CIRCSock::ReadLine(const CString& sData) { // We are changing our own nick, the clients always must see this! bIsVisible = false; SetNick(sNewNick); - m_pNetwork->PutUser(sLine); + m_pNetwork->PutUser(Message); } IRCSOCKMODULECALL(OnNickMessage(NickMsg, vFoundChans), NOTHING); @@ -749,8 +750,6 @@ void CIRCSock::ReadLine(const CString& sData) { if (pChan->IsDetached()) { return; // Don't forward this } - - sLine = ":" + Nick.GetNickMask() + " TOPIC " + pChan->GetName() + " :" + pChan->GetTopic(); } } else if (sCmd.Equals("PRIVMSG")) { // :nick!ident@host.com PRIVMSG #chan :Message @@ -878,7 +877,7 @@ void CIRCSock::ReadLine(const CString& sData) { } } - m_pNetwork->PutUser(sLine); + m_pNetwork->PutUser(Message); } void CIRCSock::SendNextCap() { From d0a58ff239f467673dcff6265eb5600e397d5488 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 13 Jul 2015 17:44:48 +0200 Subject: [PATCH 7/8] Pass CMessage to buffer playback hooks --- include/znc/Modules.h | 4 ++++ src/Chan.cpp | 16 ++++++++++------ src/IRCNetwork.cpp | 11 ++++++++--- src/Modules.cpp | 21 +++++++++++++++++++++ src/Query.cpp | 20 ++++++++++---------- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/include/znc/Modules.h b/include/znc/Modules.h index fe5a2c75..a27a1326 100644 --- a/include/znc/Modules.h +++ b/include/znc/Modules.h @@ -658,6 +658,7 @@ public: */ virtual EModRet OnChanBufferPlayLine2(CChan& Chan, CClient& Client, CString& sLine, const timeval& tv); virtual EModRet OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine); + virtual EModRet OnChanBufferPlayMessage(CMessage& Message); /** Called when a line from the query buffer is played back. * @param Client The client this line will go to. * @param sLine The raw IRC traffic line from the buffer. @@ -666,6 +667,7 @@ public: */ virtual EModRet OnPrivBufferPlayLine2(CClient& Client, CString& sLine, const timeval& tv); virtual EModRet OnPrivBufferPlayLine(CClient& Client, CString& sLine); + virtual EModRet OnPrivBufferPlayMessage(CMessage& Message); /** Called when a client successfully logged in to ZNC. */ virtual void OnClientLogin(); @@ -1219,6 +1221,8 @@ public: bool OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine); bool OnPrivBufferPlayLine2(CClient& Client, CString& sLine, const timeval& tv); bool OnPrivBufferPlayLine(CClient& Client, CString& sLine); + bool OnChanBufferPlayMessage(CMessage& Message); + bool OnPrivBufferPlayMessage(CMessage& Message); bool OnClientLogin(); bool OnClientDisconnect(); diff --git a/src/Chan.cpp b/src/Chan.cpp index 38cb1336..9c024e33 100644 --- a/src/Chan.cpp +++ b/src/Chan.cpp @@ -20,6 +20,7 @@ #include #include #include +#include using std::set; using std::vector; @@ -610,16 +611,19 @@ void CChan::SendBuffer(CClient* pClient, const CBuffer& Buffer) { size_t uSize = Buffer.Size(); for (size_t uIdx = 0; uIdx < uSize; uIdx++) { const CBufLine& BufLine = Buffer.GetBufLine(uIdx); - CString sLine = BufLine.GetLine(*pUseClient, MCString::EmptyMap); + CMessage Message(BufLine.GetLine(*pUseClient, MCString::EmptyMap)); + Message.SetChan(this); + Message.SetNetwork(m_pNetwork); + Message.SetClient(pClient); + Message.SetTime(BufLine.GetTime()); + Message.SetTags(BufLine.GetTags()); if (bBatch) { - MCString msBatchTags = CUtils::GetMessageTags(sLine); - msBatchTags["batch"] = sBatchName; - CUtils::SetMessageTags(sLine, msBatchTags); + Message.SetTag("batch", sBatchName); } bool bNotShowThisLine = false; - NETWORKMODULECALL(OnChanBufferPlayLine2(*this, *pUseClient, sLine, BufLine.GetTime()), m_pNetwork->GetUser(), m_pNetwork, nullptr, &bNotShowThisLine); + NETWORKMODULECALL(OnChanBufferPlayMessage(Message), m_pNetwork->GetUser(), m_pNetwork, nullptr, &bNotShowThisLine); if (bNotShowThisLine) continue; - m_pNetwork->PutUser(sLine, pUseClient); + m_pNetwork->PutUser(Message, pUseClient); } bSkipStatusMsg = pUseClient->HasServerTime(); diff --git a/src/IRCNetwork.cpp b/src/IRCNetwork.cpp index 452b7fd5..3cb96d63 100644 --- a/src/IRCNetwork.cpp +++ b/src/IRCNetwork.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -642,11 +643,15 @@ void CIRCNetwork::ClientConnected(CClient *pClient) { uSize = m_NoticeBuffer.Size(); for (uIdx = 0; uIdx < uSize; uIdx++) { const CBufLine& BufLine = m_NoticeBuffer.GetBufLine(uIdx); - CString sLine = BufLine.GetLine(*pClient, msParams); + CMessage Message(BufLine.GetLine(*pClient, msParams)); + Message.SetNetwork(this); + Message.SetClient(pClient); + Message.SetTime(BufLine.GetTime()); + Message.SetTags(BufLine.GetTags()); bool bContinue = false; - NETWORKMODULECALL(OnPrivBufferPlayLine2(*pClient, sLine, BufLine.GetTime()), m_pUser, this, nullptr, &bContinue); + NETWORKMODULECALL(OnPrivBufferPlayMessage(Message), m_pUser, this, nullptr, &bContinue); if (bContinue) continue; - pClient->PutClient(sLine); + pClient->PutClient(Message); } m_NoticeBuffer.Clear(); diff --git a/src/Modules.cpp b/src/Modules.cpp index 3b7f1578..21fa5485 100644 --- a/src/Modules.cpp +++ b/src/Modules.cpp @@ -660,6 +660,25 @@ CModule::EModRet CModule::OnPrivBufferPlayLine2(CClient& Client, CString& sLine, return OnPrivBufferPlayLine(Client, sLine); } +CModule::EModRet CModule::OnChanBufferPlayMessage(CMessage& Message) { + CString sOriginal, sModified; + sOriginal = sModified = Message.ToString(CMessage::ExcludeTags); + EModRet ret = OnChanBufferPlayLine2(*Message.GetChan(), *Message.GetClient(), sModified, Message.GetTime()); + if (ret == CONTINUE && sOriginal != sModified) { + Message.Parse(sModified); + } + return ret; +} +CModule::EModRet CModule::OnPrivBufferPlayMessage(CMessage& Message) { + CString sOriginal, sModified; + sOriginal = sModified = Message.ToString(CMessage::ExcludeTags); + EModRet ret = OnPrivBufferPlayLine2(*Message.GetClient(), sModified, Message.GetTime()); + if (ret == CONTINUE && sOriginal != sModified) { + Message.Parse(sModified); + } + return ret; +} + void CModule::OnClientLogin() {} void CModule::OnClientDisconnect() {} CModule::EModRet CModule::OnUserRaw(CString& sLine) { return CONTINUE; } @@ -905,6 +924,8 @@ bool CModules::OnChanBufferPlayLine2(CChan& Chan, CClient& Client, CString& sLin bool CModules::OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine) { MODHALTCHK(OnChanBufferPlayLine(Chan, Client, sLine)); } bool CModules::OnPrivBufferPlayLine2(CClient& Client, CString& sLine, const timeval& tv) { MODHALTCHK(OnPrivBufferPlayLine2(Client, sLine, tv)); } bool CModules::OnPrivBufferPlayLine(CClient& Client, CString& sLine) { MODHALTCHK(OnPrivBufferPlayLine(Client, sLine)); } +bool CModules::OnChanBufferPlayMessage(CMessage& Message) { MODHALTCHK(OnChanBufferPlayMessage(Message)); } +bool CModules::OnPrivBufferPlayMessage(CMessage& Message) { MODHALTCHK(OnPrivBufferPlayMessage(Message)); } bool CModules::OnCTCPReply(CNick& Nick, CString& sMessage) { MODHALTCHK(OnCTCPReply(Nick, sMessage)); } bool CModules::OnPrivCTCP(CNick& Nick, CString& sMessage) { MODHALTCHK(OnPrivCTCP(Nick, sMessage)); } bool CModules::OnPrivCTCPMessage(CPrivCTCP& Message) { MODHALTCHK(OnPrivCTCPMessage(Message)); } diff --git a/src/Query.cpp b/src/Query.cpp index 8cb536a3..2177d2f2 100644 --- a/src/Query.cpp +++ b/src/Query.cpp @@ -17,6 +17,7 @@ #include #include #include +#include using std::vector; @@ -55,24 +56,23 @@ void CQuery::SendBuffer(CClient* pClient, const CBuffer& Buffer) { size_t uSize = Buffer.Size(); for (size_t uIdx = 0; uIdx < uSize; uIdx++) { const CBufLine& BufLine = Buffer.GetBufLine(uIdx); - + CMessage Message(BufLine.GetLine(*pUseClient, MCString::EmptyMap)); if (!pUseClient->HasEchoMessage() && !pUseClient->HasSelfMessage()) { - CNick Sender(BufLine.GetFormat().Token(0)); - if (Sender.NickEquals(pUseClient->GetNick())) { + if (Message.GetNick().NickEquals(pUseClient->GetNick())) { continue; } } - - CString sLine = BufLine.GetLine(*pUseClient, msParams); + Message.SetNetwork(m_pNetwork); + Message.SetClient(pUseClient); + Message.SetTime(BufLine.GetTime()); + Message.SetTags(BufLine.GetTags()); if (bBatch) { - MCString msBatchTags = CUtils::GetMessageTags(sLine); - msBatchTags["batch"] = sBatchName; - CUtils::SetMessageTags(sLine, msBatchTags); + Message.SetTag("batch", sBatchName); } bool bContinue = false; - NETWORKMODULECALL(OnPrivBufferPlayLine2(*pUseClient, sLine, BufLine.GetTime()), m_pNetwork->GetUser(), m_pNetwork, nullptr, &bContinue); + NETWORKMODULECALL(OnPrivBufferPlayMessage(Message), m_pNetwork->GetUser(), m_pNetwork, nullptr, &bContinue); if (bContinue) continue; - m_pNetwork->PutUser(sLine, pUseClient); + m_pNetwork->PutUser(Message, pUseClient); } if (bBatch) { From d77d21cfb9dd71ffcfce813aed7ccfe5e08a9ac6 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sun, 16 Aug 2015 13:38:33 +0200 Subject: [PATCH 8/8] Document CClient::PutClient() --- include/znc/Client.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/include/znc/Client.h b/include/znc/Client.h index 2acba558..bbbff8ce 100644 --- a/include/znc/Client.h +++ b/include/znc/Client.h @@ -175,7 +175,45 @@ public: void SetPlaybackActive(bool bActive) { m_bPlaybackActive = bActive; } void PutIRC(const CString& sLine); + /** Sends a raw data line to the client. + * @param sLine The line to be sent. + * + * The line is first passed \e unmodified to the \ref CModule::OnSendToClient() + * module hook. If no module halts the process, the line is then sent to the client. + * + * These lines appear in the debug output in the following syntax: + * \code [time] (user/network) ZNC -> CLI [line] \endcode + */ void PutClient(const CString& sLine); + /** Sends a message to the client. + * @param Message The message to be sent. + * @note Only known and compatible message tags are sent. + * + * Not all IRC clients are capable of handling arbitrary sets of message + * tags. For example, some older versions of some popular clients were + * prepared to parse just one interesting tag, \c time, and would break + * if multiple tags were included. Thus, in order to stay compatible with + * a variety of IRC clients, ZNC has to filter out message tags that the + * client has not explicitly requested. + * + * The following table documents currently supported message tags, and + * which capabilities the client is required to have requested to receive + * the respective message tags. + * + * Message tag | Capability + * ----------- | ---------- + * \c time | \l CClient::HasServerTime() (server-time) + * \c batch | \l CClient::HasBatch() (batch) + * + * @warning Bypassing the filter may cause troubles to some older IRC clients. + * + * It is possible to bypass the filter by converting a message to a string + * using \l CMessage::ToString(), and passing the resulting raw line to the + * \l CClient::PutClient(const CString& sLine) overload: + * \code + * pClient->PutClient(Message.ToString()); + * \endcode + */ void PutClient(const CMessage& Message); unsigned int PutStatus(const CTable& table); void PutStatus(const CString& sLine);