diff --git a/Makefile.in b/Makefile.in index cfa5141b..2ba205ee 100644 --- a/Makefile.in +++ b/Makefile.in @@ -42,7 +42,7 @@ endif LIB_SRCS := ZNCString.cpp Csocket.cpp znc.cpp IRCNetwork.cpp User.cpp IRCSock.cpp \ 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 + WebModules.cpp Listener.cpp Config.cpp ZNCDebug.cpp Threads.cpp version.cpp Query.cpp LIB_SRCS := $(addprefix src/,$(LIB_SRCS)) BIN_SRCS := src/main.cpp LIB_OBJS := $(patsubst %cpp,%o,$(LIB_SRCS)) diff --git a/NOTICE b/NOTICE index cda4ce13..5e96f5dc 100644 --- a/NOTICE +++ b/NOTICE @@ -46,5 +46,6 @@ Michael "adgar" Edgar Jens-Andre "vain" Koch Heiko Hund - cyrusauth module Philippe (http://sourceforge.net/users/cycomate) - kickrejoin module +J-P Nurmi If you did something useful and want to be listed here too, add yourself and submit the patch. diff --git a/include/znc/IRCNetwork.h b/include/znc/IRCNetwork.h index 1b2615d4..171f0aeb 100644 --- a/include/znc/IRCNetwork.h +++ b/include/znc/IRCNetwork.h @@ -30,6 +30,7 @@ class CConfig; class CClient; class CConfig; class CChan; +class CQuery; class CServer; class CIRCSock; class CIRCNetworkPingTimer; @@ -96,6 +97,12 @@ public: void JoinChans(); void JoinChans(std::set& sChans); + const std::vector& GetQueries() const; + CQuery* FindQuery(const CString& sName) const; + std::vector FindQueries(const CString& sWild) const; + CQuery* AddQuery(const CString& sName); + bool DelQuery(const CString& sName); + const CString& GetChanPrefixes() const { return m_sChanPrefixes; }; void SetChanPrefixes(const CString& s) { m_sChanPrefixes = s; }; bool IsChan(const CString& sChan) const; @@ -144,9 +151,9 @@ public: void UpdateMotdBuffer(const CString& sMatch, const CString& sFormat, const CString& sText = "") { m_MotdBuffer.UpdateLine(sMatch, sFormat, sText); } void ClearMotdBuffer() { m_MotdBuffer.Clear(); } - void AddQueryBuffer(const CString& sFormat, const CString& sText = "") { m_QueryBuffer.AddLine(sFormat, sText); } - void UpdateQueryBuffer(const CString& sMatch, const CString& sFormat, const CString& sText = "") { m_QueryBuffer.UpdateLine(sMatch, sFormat, sText); } - void ClearQueryBuffer() { m_QueryBuffer.Clear(); } + void AddNoticeBuffer(const CString& sFormat, const CString& sText = "") { m_NoticeBuffer.AddLine(sFormat, sText); } + void UpdateNoticeBuffer(const CString& sMatch, const CString& sFormat, const CString& sText = "") { m_NoticeBuffer.UpdateLine(sMatch, sFormat, sText); } + void ClearNoticeBuffer() { m_NoticeBuffer.Clear(); } // !Buffers // la @@ -192,6 +199,7 @@ protected: CIRCSock* m_pIRCSock; std::vector m_vChans; + std::vector m_vQueries; CString m_sChanPrefixes; @@ -208,7 +216,7 @@ protected: CBuffer m_RawBuffer; CBuffer m_MotdBuffer; - CBuffer m_QueryBuffer; + CBuffer m_NoticeBuffer; CIRCNetworkPingTimer* m_pPingTimer; CIRCNetworkJoinTimer* m_pJoinTimer; diff --git a/include/znc/Query.h b/include/znc/Query.h new file mode 100644 index 00000000..f57f0d06 --- /dev/null +++ b/include/znc/Query.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2004-2014 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 _QUERY_H +#define _QUERY_H + +#include +#include +#include + +// Forward Declarations +class CClient; +class CIRCNetwork; +// !Forward Declarations + +class CQuery { +public: + CQuery(const CString& sName, CIRCNetwork* pNetwork); + ~CQuery(); + + // Buffer + 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 = NULL) { return m_Buffer.AddLine(sFormat, sText, ts); } + void ClearBuffer() { m_Buffer.Clear(); } + void SendBuffer(CClient* pClient); + void SendBuffer(CClient* pClient, const CBuffer& Buffer); + // !Buffer + + // Getters + const CString& GetName() const { return m_sName; } + // !Getters + +private: + CString m_sName; + CIRCNetwork* m_pNetwork; + CBuffer m_Buffer; +}; + +#endif // !_QUERY_H diff --git a/include/znc/User.h b/include/znc/User.h index b6d2de67..588dce32 100644 --- a/include/znc/User.h +++ b/include/znc/User.h @@ -126,6 +126,7 @@ public: bool DelCTCPReply(const CString& sCTCP); bool SetBufferCount(unsigned int u, bool bForce = false); void SetAutoClearChanBuffer(bool b); + void SetAutoClearQueryBuffer(bool b); void SetBeingDeleted(bool b) { m_bBeingDeleted = b; } void SetTimestampFormat(const CString& s) { m_sTimestampFormat = s; } @@ -136,6 +137,7 @@ public: void SetMaxJoins(unsigned int i) { m_uMaxJoins = i; } void SetSkinName(const CString& s) { m_sSkinName = s; } void SetMaxNetworks(unsigned int i) { m_uMaxNetworks = i; } + void SetMaxQueryBuffers(unsigned int i) { m_uMaxQueryBuffers = i; } // !Setters // Getters @@ -171,6 +173,7 @@ public: const MCString& GetCTCPReplies() const; unsigned int GetBufferCount() const; bool AutoClearChanBuffer() const; + bool AutoClearQueryBuffer() const; bool IsBeingDeleted() const { return m_bBeingDeleted; } CString GetTimezone() const { return m_sTimezone; } unsigned long long BytesRead() const { return m_uBytesRead; } @@ -179,6 +182,7 @@ public: unsigned int MaxJoins() const { return m_uMaxJoins; } CString GetSkinName() const; unsigned int MaxNetworks() const { return m_uMaxNetworks; } + unsigned int MaxQueryBuffers() const { return m_uMaxQueryBuffers; } // !Getters protected: @@ -211,6 +215,7 @@ protected: bool m_bAdmin; bool m_bDenySetBindHost; bool m_bAutoClearChanBuffer; + bool m_bAutoClearQueryBuffer; bool m_bBeingDeleted; bool m_bAppendTimestamp; bool m_bPrependTimestamp; @@ -225,6 +230,7 @@ protected: unsigned long long m_uBytesWritten; unsigned int m_uMaxJoinTries; unsigned int m_uMaxNetworks; + unsigned int m_uMaxQueryBuffers; unsigned int m_uMaxJoins; CString m_sSkinName; diff --git a/src/Client.cpp b/src/Client.cpp index 44cadf70..cf9d8e4b 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -18,6 +18,7 @@ #include #include #include +#include using std::map; using std::vector; @@ -298,20 +299,20 @@ void CClient::ReadLine(const CString& sData) { } if (m_pNetwork) { - CChan* pChan = m_pNetwork->FindChan(sTarget); - if (sCTCP.Token(0).Equals("ACTION")) { CString sMessage = sCTCP.Token(1, true); NETWORKMODULECALL(OnUserAction(sTarget, sMessage), m_pUser, m_pNetwork, this, &bReturn); if (bReturn) return; sCTCP = "ACTION " + sMessage; - if (pChan && (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline())) { - pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :\001ACTION {text}\001", sMessage); - } - - // Relay to the rest of the clients that may be connected to this user if (m_pNetwork->IsChan(sTarget)) { + CChan* pChan = m_pNetwork->FindChan(sTarget); + + if (pChan && (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline())) { + pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :\001ACTION {text}\001", sMessage); + } + + // Relay to the rest of the clients that may be connected to this user vector& vClients = GetClients(); for (unsigned int a = 0; a < vClients.size(); a++) { @@ -321,6 +322,13 @@ void CClient::ReadLine(const CString& sData) { pClient->PutClient(":" + GetNickMask() + " PRIVMSG " + sTarget + " :\001" + sCTCP + "\001"); } } + } 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); + } + } } } else { NETWORKMODULECALL(OnUserCTCP(sTarget, sCTCP), m_pUser, m_pNetwork, this, &bReturn); @@ -354,10 +362,19 @@ void CClient::ReadLine(const CString& sData) { } if (m_pNetwork) { - CChan* pChan = m_pNetwork->FindChan(sTarget); + if (m_pNetwork->IsChan(sTarget)) { + CChan* pChan = m_pNetwork->FindChan(sTarget); - if ((pChan) && (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline())) { - pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :{text}", sMsg); + if ((pChan) && (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline())) { + pChan->AddBuffer(":" + _NAMEDFMT(GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sTarget) + " :{text}", sMsg); + } + } 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); + } + } } PutIRC("PRIVMSG " + sTarget + " :" + sMsg); diff --git a/src/ClientCommand.cpp b/src/ClientCommand.cpp index dd969381..3ee1e865 100644 --- a/src/ClientCommand.cpp +++ b/src/ClientCommand.cpp @@ -20,6 +20,7 @@ #include #include #include +#include using std::vector; using std::set; @@ -1270,62 +1271,76 @@ void CClient::UserCommand(CString& sLine) { return; } - CString sChan = sLine.Token(1); + CString sBuffer = sLine.Token(1); - if (sChan.empty()) { - PutStatus("Usage: PlayBuffer <#chan>"); + if (sBuffer.empty()) { + PutStatus("Usage: PlayBuffer <#chan|query>"); return; } - CChan* pChan = m_pNetwork->FindChan(sChan); + if (m_pNetwork->IsChan(sBuffer)) { + CChan* pChan = m_pNetwork->FindChan(sBuffer); - if (!pChan) { - PutStatus("You are not on [" + sChan + "]"); - return; + if (!pChan) { + PutStatus("You are not on [" + sBuffer + "]"); + return; + } + + if (!pChan->IsOn()) { + PutStatus("You are not on [" + sBuffer + "] [trying]"); + return; + } + + if (pChan->GetBuffer().IsEmpty()) { + PutStatus("The buffer for [" + sBuffer + "] is empty"); + return; + } + + pChan->SendBuffer(this); + } else { + CQuery* pQuery = m_pNetwork->FindQuery(sBuffer); + + if (!pQuery) { + PutStatus("No active query with [" + sBuffer + "]"); + return; + } + + if (pQuery->GetBuffer().IsEmpty()) { + PutStatus("The buffer for [" + sBuffer + "] is empty"); + return; + } + + pQuery->SendBuffer(this); } - - if (!pChan->IsOn()) { - PutStatus("You are not on [" + sChan + "] [trying]"); - return; - } - - if (pChan->GetBuffer().IsEmpty()) { - PutStatus("The buffer for [" + sChan + "] is empty"); - return; - } - - pChan->SendBuffer(this); } else if (sCommand.Equals("CLEARBUFFER")) { if (!m_pNetwork) { PutStatus("You must be connected with a network to use this command"); return; } - CString sChan = sLine.Token(1); + CString sBuffer = sLine.Token(1); - if (sChan.empty()) { - PutStatus("Usage: ClearBuffer <#chan>"); + if (sBuffer.empty()) { + PutStatus("Usage: ClearBuffer <#chan|query>"); return; } - CChan* pChan = m_pNetwork->FindChan(sChan); - - if (!pChan) { - PutStatus("You are not on [" + sChan + "]"); - return; - } - - const vector& vChans = m_pNetwork->GetChans(); - vector::const_iterator it; unsigned int uMatches = 0; - for (it = vChans.begin(); it != vChans.end(); ++it) { - if (!(*it)->GetName().WildCmp(sChan)) - continue; + vector vChans = m_pNetwork->FindChans(sBuffer); + for (vector::const_iterator it = vChans.begin(); it != vChans.end(); ++it) { uMatches++; (*it)->ClearBuffer(); } - PutStatus("The buffer for [" + CString(uMatches) + "] channels matching [" + sChan + "] has been cleared"); + + vector vQueries = m_pNetwork->FindQueries(sBuffer); + for (vector::const_iterator it = vQueries.begin(); it != vQueries.end(); ++it) { + uMatches++; + + m_pNetwork->DelQuery((*it)->GetName()); + } + + PutStatus("[" + CString(uMatches) + "] buffers matching [" + sBuffer + "] have been cleared"); } else if (sCommand.Equals("CLEARALLCHANNELBUFFERS")) { if (!m_pNetwork) { PutStatus("You must be connected with a network to use this command"); @@ -1339,27 +1354,44 @@ void CClient::UserCommand(CString& sLine) { (*it)->ClearBuffer(); } PutStatus("All channel buffers have been cleared"); + } else if (sCommand.Equals("CLEARALLQUERYBUFFERS")) { + if (!m_pNetwork) { + PutStatus("You must be connected with a network to use this command"); + return; + } + + vector::const_iterator it; + vector VQueries = m_pNetwork->GetQueries(); + + for (it = VQueries.begin(); it != VQueries.end(); ++it) { + m_pNetwork->DelQuery((*it)->GetName()); + } + PutStatus("All query buffers have been cleared"); } else if (sCommand.Equals("SETBUFFER")) { if (!m_pNetwork) { PutStatus("You must be connected with a network to use this command"); return; } - CString sChan = sLine.Token(1); + CString sBuffer = sLine.Token(1); - if (sChan.empty()) { - PutStatus("Usage: SetBuffer <#chan> [linecount]"); + if (sBuffer.empty()) { + PutStatus("Usage: SetBuffer <#chan|query> [linecount]"); return; } unsigned int uLineCount = sLine.Token(2).ToUInt(); - - const vector& vChans = m_pNetwork->GetChans(); - vector::const_iterator it; unsigned int uMatches = 0, uFail = 0; - for (it = vChans.begin(); it != vChans.end(); ++it) { - if (!(*it)->GetName().WildCmp(sChan)) - continue; + vector vChans = m_pNetwork->FindChans(sBuffer); + for (vector::const_iterator it = vChans.begin(); it != vChans.end(); ++it) { + uMatches++; + + if (!(*it)->SetBufferCount(uLineCount)) + uFail++; + } + + vector vQueries = m_pNetwork->FindQueries(sBuffer); + for (vector::const_iterator it = vQueries.begin(); it != vQueries.end(); ++it) { uMatches++; if (!(*it)->SetBufferCount(uLineCount)) @@ -1367,9 +1399,9 @@ void CClient::UserCommand(CString& sLine) { } PutStatus("BufferCount for [" + CString(uMatches - uFail) + - "] channels was set to [" + CString(uLineCount) + "]"); + "] buffer was set to [" + CString(uLineCount) + "]"); if (uFail > 0) { - PutStatus("Setting BufferCount failed for [" + CString(uFail) + "] channels, " + PutStatus("Setting BufferCount failed for [" + CString(uFail) + "] buffers, " "max buffer count is " + CString(CZNC::Get().GetMaxBufferSize())); } } else if (m_pUser->IsAdmin() && sCommand.Equals("TRAFFIC")) { @@ -1620,22 +1652,26 @@ void CClient::HelpUser() { Table.AddRow(); Table.SetCell("Command", "PlayBuffer"); - Table.SetCell("Arguments", "<#chan>"); - Table.SetCell("Description", "Play back the buffer for a given channel"); + Table.SetCell("Arguments", "<#chan|query>"); + Table.SetCell("Description", "Play back the specified buffer"); Table.AddRow(); Table.SetCell("Command", "ClearBuffer"); - Table.SetCell("Arguments", "<#chan>"); - Table.SetCell("Description", "Clear the buffer for a given channel"); + Table.SetCell("Arguments", "<#chan|query>"); + Table.SetCell("Description", "Clear the specified buffer"); Table.AddRow(); Table.SetCell("Command", "ClearAllChannelBuffers"); Table.SetCell("Description", "Clear the channel buffers"); + Table.AddRow(); + Table.SetCell("Command", "ClearAllQueryBuffers"); + Table.SetCell("Description", "Clear the query buffers"); + Table.AddRow(); Table.SetCell("Command", "SetBuffer"); - Table.SetCell("Arguments", "<#chan> [linecount]"); - Table.SetCell("Description", "Set the buffer count for a channel"); + Table.SetCell("Arguments", "<#chan|query> [linecount]"); + Table.SetCell("Description", "Set the buffer count"); if (m_pUser->IsAdmin()) { Table.AddRow(); diff --git a/src/IRCNetwork.cpp b/src/IRCNetwork.cpp index ec15fcd7..957f4f27 100644 --- a/src/IRCNetwork.cpp +++ b/src/IRCNetwork.cpp @@ -21,6 +21,7 @@ #include #include #include +#include using std::vector; using std::set; @@ -116,7 +117,7 @@ CIRCNetwork::CIRCNetwork(CUser *pUser, const CString& sName) { m_RawBuffer.SetLineCount(100, true); // This should be more than enough raws, especially since we are buffering the MOTD separately m_MotdBuffer.SetLineCount(200, true); // This should be more than enough motd lines - m_QueryBuffer.SetLineCount(250, true); + m_NoticeBuffer.SetLineCount(250, true); m_pPingTimer = new CIRCNetworkPingTimer(this); CZNC::Get().GetManager().AddCron(m_pPingTimer); @@ -142,7 +143,7 @@ CIRCNetwork::CIRCNetwork(CUser *pUser, const CIRCNetwork &Network) { m_RawBuffer.SetLineCount(100, true); // This should be more than enough raws, especially since we are buffering the MOTD separately m_MotdBuffer.SetLineCount(200, true); // This should be more than enough motd lines - m_QueryBuffer.SetLineCount(250, true); + m_NoticeBuffer.SetLineCount(250, true); Clone(Network); } @@ -281,6 +282,12 @@ CIRCNetwork::~CIRCNetwork() { } m_vChans.clear(); + // Delete Queries + for (vector::const_iterator it = m_vQueries.begin(); it != m_vQueries.end(); ++it) { + delete *it; + } + m_vQueries.clear(); + SetUser(NULL); // Make sure we are not in the connection queue @@ -589,15 +596,26 @@ void CIRCNetwork::ClientConnected(CClient *pClient) { } } - uSize = m_QueryBuffer.Size(); + bool bClearQuery = m_pUser->AutoClearQueryBuffer(); + for (vector::const_iterator it = m_vQueries.begin(); it != m_vQueries.end(); ++it) { + (*it)->SendBuffer(pClient); + if (bClearQuery) { + delete *it; + } + } + if (bClearQuery) { + m_vQueries.clear(); + } + + uSize = m_NoticeBuffer.Size(); for (uIdx = 0; uIdx < uSize; uIdx++) { - CString sLine = m_QueryBuffer.GetLine(uIdx, *pClient, msParams); + CString sLine = m_NoticeBuffer.GetLine(uIdx, *pClient, msParams); bool bContinue = false; NETWORKMODULECALL(OnPrivBufferPlayLine(*pClient, sLine), m_pUser, this, NULL, &bContinue); if (bContinue) continue; pClient->PutClient(sLine); } - m_QueryBuffer.Clear(); + m_NoticeBuffer.Clear(); // Tell them why they won't connect if (!GetIRCConnectEnabled()) @@ -855,6 +873,64 @@ bool CIRCNetwork::IsChan(const CString& sChan) const { return GetChanPrefixes().find(sChan[0]) != CString::npos; } +// Queries + +const vector& CIRCNetwork::GetQueries() const { return m_vQueries; } + +CQuery* CIRCNetwork::FindQuery(const CString& sName) const { + for (unsigned int a = 0; a < m_vQueries.size(); a++) { + CQuery* pQuery = m_vQueries[a]; + if (sName.Equals(pQuery->GetName())) { + return pQuery; + } + } + + return NULL; +} + +std::vector CIRCNetwork::FindQueries(const CString& sWild) const { + std::vector vQueries; + vQueries.reserve(m_vQueries.size()); + for (std::vector::const_iterator it = m_vQueries.begin(); it != m_vQueries.end(); ++it) { + if ((*it)->GetName().WildCmp(sWild)) + vQueries.push_back(*it); + } + return vQueries; +} + +CQuery* CIRCNetwork::AddQuery(const CString& sName) { + if (sName.empty()) { + return NULL; + } + + CQuery* pQuery = FindQuery(sName); + if (!pQuery) { + pQuery = new CQuery(sName, this); + m_vQueries.push_back(pQuery); + + if (m_pUser->MaxQueryBuffers() > 0) { + while (m_vQueries.size() > m_pUser->MaxQueryBuffers()) { + delete *m_vQueries.begin(); + m_vQueries.erase(m_vQueries.begin()); + } + } + } + + return pQuery; +} + +bool CIRCNetwork::DelQuery(const CString& sName) { + for (vector::iterator a = m_vQueries.begin(); a != m_vQueries.end(); ++a) { + if (sName.Equals((*a)->GetName())) { + delete *a; + m_vQueries.erase(a); + return true; + } + } + + return false; +} + // Server list const vector& CIRCNetwork::GetServers() const { return m_vServers; } diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp index d7f0a42a..172c9aea 100644 --- a/src/IRCSock.cpp +++ b/src/IRCSock.cpp @@ -19,6 +19,7 @@ #include #include #include +#include using std::set; using std::vector; @@ -773,7 +774,7 @@ void CIRCSock::ReadLine(const CString& sData) { CString sMsg = sRest.Token(0, true).TrimPrefix_n(); if (!m_pNetwork->IsUserOnline()) { - m_pNetwork->AddQueryBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " WALLOPS :{text}", sMsg); + m_pNetwork->AddNoticeBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " WALLOPS :{text}", sMsg); } } else if (sCmd.Equals("CAP")) { // CAPs are supported only before authorization. @@ -885,9 +886,11 @@ bool CIRCSock::OnPrivCTCP(CNick& Nick, CString& sMessage) { IRCSOCKMODULECALL(OnPrivAction(Nick, sMessage), &bResult); if (bResult) return true; - if (!m_pNetwork->IsUserOnline()) { - // If the user is detached, add to the buffer - m_pNetwork->AddQueryBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :\001ACTION {text}\001", sMessage); + if (!m_pNetwork->IsUserOnline() || !m_pNetwork->GetUser()->AutoClearQueryBuffer()) { + CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); + if (pQuery) { + pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :\001ACTION {text}\001", sMessage); + } } sMessage = "ACTION " + sMessage; @@ -948,7 +951,7 @@ bool CIRCSock::OnPrivNotice(CNick& Nick, CString& sMessage) { if (!m_pNetwork->IsUserOnline()) { // If the user is detached, add to the buffer - m_pNetwork->AddQueryBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " NOTICE {target} :{text}", sMessage); + m_pNetwork->AddNoticeBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " NOTICE {target} :{text}", sMessage); } return false; @@ -959,9 +962,11 @@ bool CIRCSock::OnPrivMsg(CNick& Nick, CString& sMessage) { IRCSOCKMODULECALL(OnPrivMsg(Nick, sMessage), &bResult); if (bResult) return true; - if (!m_pNetwork->IsUserOnline()) { - // If the user is detached, add to the buffer - m_pNetwork->AddQueryBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :{text}", sMessage); + if (!m_pNetwork->IsUserOnline() || !m_pNetwork->GetUser()->AutoClearQueryBuffer()) { + CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); + if (pQuery) { + pQuery->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :{text}", sMessage); + } } return false; diff --git a/src/Query.cpp b/src/Query.cpp new file mode 100644 index 00000000..500e89e6 --- /dev/null +++ b/src/Query.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2004-2014 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 + +using std::vector; + +CQuery::CQuery(const CString& sName, CIRCNetwork* pNetwork) { + m_sName = sName; + m_pNetwork = pNetwork; + + SetBufferCount(m_pNetwork->GetUser()->GetBufferCount(), true); +} + +CQuery::~CQuery() { +} + +void CQuery::SendBuffer(CClient* pClient) { + SendBuffer(pClient, m_Buffer); +} + +void CQuery::SendBuffer(CClient* pClient, const CBuffer& Buffer) { + if (m_pNetwork && m_pNetwork->IsUserAttached()) { + // Based on CChan::SendBuffer() + if (!Buffer.IsEmpty()) { + MCString msParams; + msParams["target"] = m_pNetwork->GetIRCNick().GetNick(); + const vector & vClients = m_pNetwork->GetClients(); + for (size_t uClient = 0; uClient < vClients.size(); ++uClient) { + CClient * pUseClient = (pClient ? pClient : vClients[uClient]); + + size_t uSize = Buffer.Size(); + for (size_t uIdx = 0; uIdx < uSize; uIdx++) { + CString sLine = Buffer.GetLine(uIdx, *pUseClient, msParams); + bool bContinue = false; + NETWORKMODULECALL(OnPrivBufferPlayLine(*pUseClient, sLine), m_pNetwork->GetUser(), m_pNetwork, NULL, &bContinue); + if (bContinue) continue; + m_pNetwork->PutUser(sLine, pUseClient); + } + + if (pClient) + break; + } + } + } +} diff --git a/src/User.cpp b/src/User.cpp index 930d211e..16b8526b 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -71,6 +71,8 @@ CUser::CUser(const CString& sUserName) m_uBufferCount = 50; m_uMaxJoinTries = 10; m_bAutoClearChanBuffer = true; + m_bAutoClearQueryBuffer = true; + m_uMaxQueryBuffers = 50; m_uMaxJoins = 0; m_bBeingDeleted = false; m_sTimestampFormat = "[%H:%M:%S]"; @@ -130,12 +132,14 @@ bool CUser::ParseConfig(CConfig* pConfig, CString& sError) { TOption UIntOptions[] = { { "jointries", &CUser::SetJoinTries }, { "maxnetworks", &CUser::SetMaxNetworks }, + { "maxquerybuffers", &CUser::SetMaxQueryBuffers }, { "maxjoins", &CUser::SetMaxJoins }, }; size_t numUIntOptions = sizeof(UIntOptions) / sizeof(UIntOptions[0]); TOption BoolOptions[] = { { "keepbuffer", &CUser::SetKeepBuffer }, // XXX compatibility crap from pre-0.207 { "autoclearchanbuffer", &CUser::SetAutoClearChanBuffer }, + { "autoclearquerybuffer", &CUser::SetAutoClearQueryBuffer }, { "multiclients", &CUser::SetMultiClients }, { "denyloadmod", &CUser::SetDenyLoadMod }, { "admin", &CUser::SetAdmin }, @@ -699,6 +703,7 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) { SetBufferCount(User.GetBufferCount(), true); SetJoinTries(User.JoinTries()); SetMaxNetworks(User.MaxNetworks()); + SetMaxQueryBuffers(User.MaxQueryBuffers()); SetMaxJoins(User.MaxJoins()); SetClientEncoding(User.GetClientEncoding()); @@ -736,6 +741,7 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) { // Flags SetAutoClearChanBuffer(User.AutoClearChanBuffer()); + SetAutoClearQueryBuffer(User.AutoClearQueryBuffer()); SetMultiClients(User.MultiClients()); SetDenyLoadMod(User.DenyLoadMod()); SetAdmin(User.IsAdmin()); @@ -886,6 +892,7 @@ CConfig CUser::ToConfig() { config.AddKeyValuePair("ChanModes", GetDefaultChanModes()); config.AddKeyValuePair("Buffer", CString(GetBufferCount())); config.AddKeyValuePair("AutoClearChanBuffer", CString(AutoClearChanBuffer())); + config.AddKeyValuePair("AutoClearQueryBuffer", CString(AutoClearQueryBuffer())); config.AddKeyValuePair("MultiClients", CString(MultiClients())); config.AddKeyValuePair("DenyLoadMod", CString(DenyLoadMod())); config.AddKeyValuePair("Admin", CString(IsAdmin())); @@ -896,6 +903,7 @@ CConfig CUser::ToConfig() { config.AddKeyValuePair("Timezone", m_sTimezone); config.AddKeyValuePair("JoinTries", CString(m_uMaxJoinTries)); config.AddKeyValuePair("MaxNetworks", CString(m_uMaxNetworks)); + config.AddKeyValuePair("MaxQueryBuffers", CString(m_uMaxQueryBuffers)); config.AddKeyValuePair("MaxJoins", CString(m_uMaxJoins)); config.AddKeyValuePair("ClientEncoding", GetClientEncoding()); @@ -1109,6 +1117,7 @@ void CUser::SetDefaultChanModes(const CString& s) { m_sDefaultChanModes = s; } void CUser::SetClientEncoding(const CString& s) { m_sClientEncoding = s; } void CUser::SetQuitMsg(const CString& s) { m_sQuitMsg = s; } void CUser::SetAutoClearChanBuffer(bool b) { m_bAutoClearChanBuffer = b; } +void CUser::SetAutoClearQueryBuffer(bool b) { m_bAutoClearQueryBuffer = b; } bool CUser::SetBufferCount(unsigned int u, bool bForce) { if (!bForce && u > CZNC::Get().GetMaxBufferSize()) @@ -1185,6 +1194,7 @@ CString CUser::GetQuitMsg() const { return (!m_sQuitMsg.Trim_n().empty()) ? m_sQ const MCString& CUser::GetCTCPReplies() const { return m_mssCTCPReplies; } unsigned int CUser::GetBufferCount() const { return m_uBufferCount; } bool CUser::AutoClearChanBuffer() const { return m_bAutoClearChanBuffer; } +bool CUser::AutoClearQueryBuffer() const { return m_bAutoClearQueryBuffer; } //CString CUser::GetSkinName() const { return (!m_sSkinName.empty()) ? m_sSkinName : CZNC::Get().GetSkinName(); } CString CUser::GetSkinName() const { return m_sSkinName; } const CString& CUser::GetUserPath() const { if (!CFile::Exists(m_sUserPath)) { CDir::MakeDir(m_sUserPath); } return m_sUserPath; } diff --git a/src/znc.cpp b/src/znc.cpp index f8b1cbcd..f22584d1 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -726,7 +726,7 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { unsigned int uBufferCount = 0; - CUtils::GetNumInput("Number of lines to buffer per channel", uBufferCount, 0, ~0, 50); + CUtils::GetNumInput("Number of lines to buffer per channel or query", uBufferCount, 0, ~0, 50); if (uBufferCount) { vsLines.push_back("\tBuffer = " + CString(uBufferCount)); } @@ -735,6 +735,11 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { } else { vsLines.push_back("\tAutoClearChanBuffer = false"); } + if (CUtils::GetBoolInput("Would you like to clear query buffers after replay?", true)) { + vsLines.push_back("\tAutoClearQueryBuffer = true"); + } else { + vsLines.push_back("\tAutoClearQueryBuffer = false"); + } CUtils::GetInput("Default channel modes", sAnswer, "+stn"); if (!sAnswer.empty()) {