diff --git a/include/znc/Client.h b/include/znc/Client.h index e7291c7c..aaa9960d 100644 --- a/include/znc/Client.h +++ b/include/znc/Client.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -278,6 +279,19 @@ private: unsigned int AttachChans(const std::set& sChans); unsigned int DetachChans(const std::set& sChans); + bool OnActionMessage(CActionMessage& Message); + bool OnCTCPMessage(CCTCPMessage& Message); + bool OnJoinMessage(CJoinMessage& Message); + bool OnModeMessage(CModeMessage& Message); + bool OnNoticeMessage(CNoticeMessage& Message); + bool OnPartMessage(CPartMessage& Message); + bool OnPingMessage(CMessage& Message); + bool OnPongMessage(CMessage& Message); + bool OnQuitMessage(CQuitMessage& Message); + bool OnTextMessage(CTextMessage& Message); + bool OnTopicMessage(CTopicMessage& Message); + bool OnOtherMessage(CMessage& Message); + protected: bool m_bGotPass; bool m_bGotNick; diff --git a/src/Client.cpp b/src/Client.cpp index be4be429..cdab0747 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -171,342 +171,46 @@ void CClient::ReadLine(const CString& sData) { return; } - if (sCommand.Equals("ZNC")) { - CString sTarget = Message.GetParam(0); - CString sModCommand; - - if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { - sModCommand = Message.GetParams(1); - } else { - sTarget = "status"; - sModCommand = Message.GetParams(0); - } - - if (sTarget.Equals("status")) { - if (sModCommand.empty()) - PutStatus("Hello. How may I help you?"); - else - UserCommand(sModCommand); - } else { - if (sModCommand.empty()) - CALLMOD(sTarget, this, m_pUser, m_pNetwork, PutModule("Hello. How may I help you?")) - else - CALLMOD(sTarget, this, m_pUser, m_pNetwork, OnModCommand(sModCommand)) - } - return; - } else if (Message.GetType() == CMessage::Type::Ping) { - // All PONGs are generated by ZNC. We will still forward this to - // the ircd, but all PONGs from irc will be blocked. - if (!Message.GetParams().empty()) - PutClient(":irc.znc.in PONG irc.znc.in " + Message.GetParams(0)); - else - PutClient(":irc.znc.in PONG irc.znc.in"); - } else if (Message.GetType() == CMessage::Type::Pong) { - // Block PONGs, we already responded to the pings - return; - } else if (Message.GetType() == CMessage::Type::Quit) { - CQuitMessage& QuitMsg = static_cast(Message); - NETWORKMODULECALL(OnUserQuitMessage(QuitMsg), m_pUser, m_pNetwork, this, &bReturn); - if (bReturn) return; - Close(Csock::CLT_AFTERWRITE); // Treat a client quit as a detach - return; // Don't forward this msg. We don't want the client getting us disconnected. - } else if (sCommand.Equals("PROTOCTL")) { - for (const CString& sParam : Message.GetParams()) { - if (sParam == "NAMESX") { - m_bNamesx = true; - } else if (sParam == "UHNAMES") { - m_bUHNames = true; - } - } - return; // If the server understands it, we already enabled namesx / uhnames - } else if (Message.GetType() == CMessage::Type::Notice) { - CNoticeMessage& NoticeMsg = static_cast(Message); - CString sTargets = NoticeMsg.GetTarget(); - - VCString vTargets; - sTargets.Split(",", vTargets, false); - - for (CString& sTarget : vTargets) { - NoticeMsg.SetTarget(sTarget); - - if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { - if (!sTarget.Equals("status")) { - CALLMOD(sTarget, this, m_pUser, m_pNetwork, OnModNotice(NoticeMsg.GetText())); - } - continue; - } - - bool bContinue = false; - NETWORKMODULECALL(OnUserNoticeMessage(NoticeMsg), m_pUser, m_pNetwork, this, &bContinue); - if (bContinue) continue; - - if (!GetIRCSock()) { - // Some lagmeters do a NOTICE to their own nick, ignore those. - if (!sTarget.Equals(m_sNick)) - PutStatus("Your notice to [" + NoticeMsg.GetTarget() + "] got lost, " - "you are not connected to IRC!"); - continue; - } - - if (m_pNetwork) { - AddBuffer(NoticeMsg); - EchoMessage(NoticeMsg); - PutIRC(NoticeMsg.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); - } - } - - return; - } else if (Message.GetType() == CMessage::Type::CTCP) { - CCTCPMessage& CTCPMsg = static_cast(Message); - CString sTargets = CTCPMsg.GetTarget(); - - VCString vTargets; - sTargets.Split(",", vTargets, false); - - if (CTCPMsg.IsReply()) { - CString sCTCP = CTCPMsg.GetText(); - if (sCTCP.Token(0) == "VERSION") { - CTCPMsg.SetText(sCTCP + " via " + CZNC::GetTag(false)); - } - } - - for (CString& sTarget : vTargets) { - CTCPMsg.SetTarget(sTarget); - - bool bContinue = false; - if (CTCPMsg.IsReply()) { - NETWORKMODULECALL(OnUserCTCPReplyMessage(CTCPMsg), m_pUser, m_pNetwork, this, &bContinue); - } else { - NETWORKMODULECALL(OnUserCTCPMessage(CTCPMsg), m_pUser, m_pNetwork, this, &bContinue); - } - if (bContinue) continue; - - if (!GetIRCSock()) { - // Some lagmeters do a NOTICE to their own nick, ignore those. - if (!sTarget.Equals(m_sNick)) - PutStatus("Your CTCP to [" + CTCPMsg.GetTarget() + "] got lost, " - "you are not connected to IRC!"); - continue; - } - - if (m_pNetwork) { - AddBuffer(CTCPMsg); - EchoMessage(CTCPMsg); - PutIRC(CTCPMsg.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); - } - } - - return; - } else if (Message.GetType() == CMessage::Type::Text) { - CTextMessage& TextMsg = static_cast(Message); - CString sTargets = TextMsg.GetTarget(); - - VCString vTargets; - sTargets.Split(",", vTargets, false); - - for (CString& sTarget : vTargets) { - TextMsg.SetTarget(sTarget); - - if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { - EchoMessage(Message); - if (sTarget.Equals("status")) { - CString sMsg = TextMsg.GetText(); - UserCommand(sMsg); - } else { - CALLMOD(sTarget, this, m_pUser, m_pNetwork, OnModCommand(TextMsg.GetText())); - } - continue; - } - - bool bContinue = false; - NETWORKMODULECALL(OnUserTextMessage(TextMsg), m_pUser, m_pNetwork, this, &bContinue); - if (bContinue) continue; - - if (!GetIRCSock()) { - // Some lagmeters do a PRIVMSG to their own nick, ignore those. - if (!sTarget.Equals(m_sNick)) - PutStatus("Your message to [" + TextMsg.GetTarget() + "] got lost, " - "you are not connected to IRC!"); - continue; - } - - if (m_pNetwork) { - AddBuffer(TextMsg); - EchoMessage(TextMsg); - PutIRC(TextMsg.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); - } - } - - return; - } else if (Message.GetType() == CMessage::Type::Action) { - CActionMessage& ActionMsg = static_cast(Message); - CString sTargets = ActionMsg.GetTarget(); - - VCString vTargets; - sTargets.Split(",", vTargets, false); - - for (CString& sTarget : vTargets) { - ActionMsg.SetTarget(sTarget); - - bool bContinue = false; - NETWORKMODULECALL(OnUserActionMessage(ActionMsg), m_pUser, m_pNetwork, this, &bContinue); - if (bContinue) continue; - - if (m_pNetwork) { - AddBuffer(ActionMsg); - EchoMessage(ActionMsg); - PutIRC(ActionMsg.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); - } - } - - return; + switch (Message.GetType()) { + case CMessage::Type::Action: + bReturn = OnActionMessage(static_cast(Message)); + break; + case CMessage::Type::CTCP: + bReturn = OnCTCPMessage(static_cast(Message)); + break; + case CMessage::Type::Join: + bReturn = OnJoinMessage(static_cast(Message)); + break; + case CMessage::Type::Mode: + bReturn = OnModeMessage(static_cast(Message)); + break; + case CMessage::Type::Notice: + bReturn = OnNoticeMessage(static_cast(Message)); + break; + case CMessage::Type::Part: + bReturn = OnPartMessage(static_cast(Message)); + break; + case CMessage::Type::Ping: + bReturn = OnPingMessage(Message); + break; + case CMessage::Type::Pong: + bReturn = OnPongMessage(Message); + break; + case CMessage::Type::Quit: + bReturn = OnQuitMessage(static_cast(Message)); + break; + case CMessage::Type::Text: + bReturn = OnTextMessage(static_cast(Message)); + break; + case CMessage::Type::Topic: + bReturn = OnTopicMessage(static_cast(Message)); + break; + default: + bReturn = OnOtherMessage(Message); + break; } - if (sCommand.Equals("ATTACH")) { - CString sPatterns = sLine.Token(1, true); - - if (sPatterns.empty()) { - PutStatusNotice("Usage: /attach <#chans|queries>"); - return; - } - - set sChans = MatchChans(sPatterns); - unsigned int uAttachedChans = AttachChans(sChans); - - PutStatusNotice("There were [" + CString(sChans.size()) + "] channels matching [" + sPatterns + "]"); - PutStatusNotice("Attached [" + CString(uAttachedChans) + "] channels"); - - return; - } else if (sCommand.Equals("DETACH")) { - if (!m_pNetwork) { - return; - } - - CString sPatterns = Message.GetParams(0); - - if (sPatterns.empty()) { - PutStatusNotice("Usage: /detach <#chans>"); - return; - } - - set sChans = MatchChans(sPatterns); - unsigned int uDetached = DetachChans(sChans); - - PutStatusNotice("There were [" + CString(sChans.size()) + "] channels matching [" + sPatterns + "]"); - PutStatusNotice("Detached [" + CString(uDetached) + "] channels"); - - return; - } else if (Message.GetType() == CMessage::Type::Join) { - CJoinMessage& JoinMsg = static_cast(Message); - CString sChans = JoinMsg.GetTarget(); - CString sKeys = JoinMsg.GetKey(); - - VCString vsChans; - sChans.Split(",", vsChans, false); - sChans.clear(); - - VCString vsKeys; - sKeys.Split(",", vsKeys, true); - sKeys.clear(); - - for (unsigned int a = 0; a < vsChans.size(); a++) { - JoinMsg.SetTarget(vsChans[a]); - JoinMsg.SetKey((a < vsKeys.size()) ? vsKeys[a] : ""); - bool bContinue = false; - NETWORKMODULECALL(OnUserJoinMessage(JoinMsg), m_pUser, m_pNetwork, this, &bContinue); - if (bContinue) continue; - - CString sChannel = JoinMsg.GetTarget(); - CString sKey = JoinMsg.GetKey(); - - CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChannel) : nullptr; - if (pChan) { - if (pChan->IsDetached()) - pChan->AttachUser(this); - else - pChan->JoinUser(sKey); - continue; - } - - if (!sChannel.empty()) { - sChans += (sChans.empty()) ? sChannel : CString("," + sChannel); - - if (!vsKeys.empty()) { - sKeys += (sKeys.empty()) ? sKey : CString("," + sKey); - } - } - } - - if (sChans.empty()) { - return; - } - - JoinMsg.SetTarget(sChans); - JoinMsg.SetKey(sKeys); - } else if (Message.GetType() == CMessage::Type::Part) { - CPartMessage& PartMsg = static_cast(Message); - CString sChans = PartMsg.GetTarget(); - - VCString vsChans; - sChans.Split(",", vsChans, false); - sChans.clear(); - - for (CString& sChan : vsChans) { - bool bContinue = false; - PartMsg.SetTarget(sChan); - NETWORKMODULECALL(OnUserPartMessage(PartMsg), m_pUser, m_pNetwork, this, &bContinue); - if (bContinue) continue; - - sChan = PartMsg.GetTarget(); - - CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChan) : nullptr; - - if (pChan && !pChan->IsOn()) { - PutStatusNotice("Removing channel [" + sChan + "]"); - m_pNetwork->DelChan(sChan); - } else { - sChans += (sChans.empty()) ? sChan : CString("," + sChan); - } - } - - if (sChans.empty()) { - return; - } - - PartMsg.SetTarget(sChans); - } else if (Message.GetType() == CMessage::Type::Topic) { - CTopicMessage& TopicMsg = static_cast(Message); - CString sChan = TopicMsg.GetTarget(); - CString sTopic = TopicMsg.GetTopic(); - - if (!sTopic.empty()) { - NETWORKMODULECALL(OnUserTopicMessage(TopicMsg), m_pUser, m_pNetwork, this, &bReturn); - if (bReturn) return; - } else { - NETWORKMODULECALL(OnUserTopicRequest(sChan), m_pUser, m_pNetwork, this, &bReturn); - if (bReturn) return; - TopicMsg.SetTarget(sChan); - } - } else if (Message.GetType() == CMessage::Type::Mode) { - CModeMessage& ModeMsg = static_cast(Message); - CString sTarget = ModeMsg.GetTarget(); - CString sModes = ModeMsg.GetModes(); - - if (m_pNetwork && m_pNetwork->IsChan(sTarget) && sModes.empty()) { - // If we are on that channel and already received a - // /mode reply from the server, we can answer this - // request ourself. - - CChan *pChan = m_pNetwork->FindChan(sTarget); - if (pChan && pChan->IsOn() && !pChan->GetModeString().empty()) { - PutClient(":" + m_pNetwork->GetIRCServer() + " 324 " + GetNick() + " " + sTarget + " " + pChan->GetModeString()); - if (pChan->GetCreationDate() > 0) { - PutClient(":" + m_pNetwork->GetIRCServer() + " 329 " + GetNick() + " " + sTarget + " " + CString(pChan->GetCreationDate())); - } - return; - } - } - } + if (bReturn) return; PutIRC(Message.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); } @@ -1174,3 +878,380 @@ unsigned int CClient::DetachChans(const std::set& sChans) } return uDetached; } + +bool CClient::OnActionMessage(CActionMessage& Message) +{ + CString sTargets = Message.GetTarget(); + + VCString vTargets; + sTargets.Split(",", vTargets, false); + + for (CString& sTarget : vTargets) { + Message.SetTarget(sTarget); + + bool bContinue = false; + NETWORKMODULECALL(OnUserActionMessage(Message), m_pUser, m_pNetwork, this, &bContinue); + if (bContinue) continue; + + if (m_pNetwork) { + AddBuffer(Message); + EchoMessage(Message); + PutIRC(Message.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); + } + } + + return true; +} + +bool CClient::OnCTCPMessage(CCTCPMessage& Message) +{ + CString sTargets = Message.GetTarget(); + + VCString vTargets; + sTargets.Split(",", vTargets, false); + + if (Message.IsReply()) { + CString sCTCP = Message.GetText(); + if (sCTCP.Token(0) == "VERSION") { + Message.SetText(sCTCP + " via " + CZNC::GetTag(false)); + } + } + + for (CString& sTarget : vTargets) { + Message.SetTarget(sTarget); + + bool bContinue = false; + if (Message.IsReply()) { + NETWORKMODULECALL(OnUserCTCPReplyMessage(Message), m_pUser, m_pNetwork, this, &bContinue); + } else { + NETWORKMODULECALL(OnUserCTCPMessage(Message), m_pUser, m_pNetwork, this, &bContinue); + } + if (bContinue) continue; + + if (!GetIRCSock()) { + // Some lagmeters do a NOTICE to their own nick, ignore those. + if (!sTarget.Equals(m_sNick)) + PutStatus("Your CTCP to [" + Message.GetTarget() + "] got lost, " + "you are not connected to IRC!"); + continue; + } + + if (m_pNetwork) { + AddBuffer(Message); + EchoMessage(Message); + PutIRC(Message.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); + } + } + + return true; +} + +bool CClient::OnJoinMessage(CJoinMessage& Message) +{ + CString sChans = Message.GetTarget(); + CString sKeys = Message.GetKey(); + + VCString vsChans; + sChans.Split(",", vsChans, false); + sChans.clear(); + + VCString vsKeys; + sKeys.Split(",", vsKeys, true); + sKeys.clear(); + + for (unsigned int a = 0; a < vsChans.size(); a++) { + Message.SetTarget(vsChans[a]); + Message.SetKey((a < vsKeys.size()) ? vsKeys[a] : ""); + bool bContinue = false; + NETWORKMODULECALL(OnUserJoinMessage(Message), m_pUser, m_pNetwork, this, &bContinue); + if (bContinue) continue; + + CString sChannel = Message.GetTarget(); + CString sKey = Message.GetKey(); + + CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChannel) : nullptr; + if (pChan) { + if (pChan->IsDetached()) + pChan->AttachUser(this); + else + pChan->JoinUser(sKey); + continue; + } + + if (!sChannel.empty()) { + sChans += (sChans.empty()) ? sChannel : CString("," + sChannel); + + if (!vsKeys.empty()) { + sKeys += (sKeys.empty()) ? sKey : CString("," + sKey); + } + } + } + + Message.SetTarget(sChans); + Message.SetKey(sKeys); + + return sChans.empty(); +} + +bool CClient::OnModeMessage(CModeMessage& Message) +{ + CString sTarget = Message.GetTarget(); + CString sModes = Message.GetModes(); + + if (m_pNetwork && m_pNetwork->IsChan(sTarget) && sModes.empty()) { + // If we are on that channel and already received a + // /mode reply from the server, we can answer this + // request ourself. + + CChan *pChan = m_pNetwork->FindChan(sTarget); + if (pChan && pChan->IsOn() && !pChan->GetModeString().empty()) { + PutClient(":" + m_pNetwork->GetIRCServer() + " 324 " + GetNick() + " " + sTarget + " " + pChan->GetModeString()); + if (pChan->GetCreationDate() > 0) { + PutClient(":" + m_pNetwork->GetIRCServer() + " 329 " + GetNick() + " " + sTarget + " " + CString(pChan->GetCreationDate())); + } + return true; + } + } + + return false; +} + +bool CClient::OnNoticeMessage(CNoticeMessage& Message) +{ + CString sTargets = Message.GetTarget(); + + VCString vTargets; + sTargets.Split(",", vTargets, false); + + for (CString& sTarget : vTargets) { + Message.SetTarget(sTarget); + + if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { + if (!sTarget.Equals("status")) { + CALLMOD(sTarget, this, m_pUser, m_pNetwork, OnModNotice(Message.GetText())); + } + continue; + } + + bool bContinue = false; + NETWORKMODULECALL(OnUserNoticeMessage(Message), m_pUser, m_pNetwork, this, &bContinue); + if (bContinue) continue; + + if (!GetIRCSock()) { + // Some lagmeters do a NOTICE to their own nick, ignore those. + if (!sTarget.Equals(m_sNick)) + PutStatus("Your notice to [" + Message.GetTarget() + "] got lost, " + "you are not connected to IRC!"); + continue; + } + + if (m_pNetwork) { + AddBuffer(Message); + EchoMessage(Message); + PutIRC(Message.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); + } + } + + return true; +} + +bool CClient::OnPartMessage(CPartMessage& Message) +{ + CString sChans = Message.GetTarget(); + + VCString vsChans; + sChans.Split(",", vsChans, false); + sChans.clear(); + + for (CString& sChan : vsChans) { + bool bContinue = false; + Message.SetTarget(sChan); + NETWORKMODULECALL(OnUserPartMessage(Message), m_pUser, m_pNetwork, this, &bContinue); + if (bContinue) continue; + + sChan = Message.GetTarget(); + + CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChan) : nullptr; + + if (pChan && !pChan->IsOn()) { + PutStatusNotice("Removing channel [" + sChan + "]"); + m_pNetwork->DelChan(sChan); + } else { + sChans += (sChans.empty()) ? sChan : CString("," + sChan); + } + } + + if (sChans.empty()) { + return true; + } + + Message.SetTarget(sChans); + + return false; +} + +bool CClient::OnPingMessage(CMessage& Message) +{ + // All PONGs are generated by ZNC. We will still forward this to + // the ircd, but all PONGs from irc will be blocked. + if (!Message.GetParams().empty()) + PutClient(":irc.znc.in PONG irc.znc.in " + Message.GetParams(0)); + else + PutClient(":irc.znc.in PONG irc.znc.in"); + return false; +} + +bool CClient::OnPongMessage(CMessage& Message) +{ + // Block PONGs, we already responded to the pings + return true; +} + +bool CClient::OnQuitMessage(CQuitMessage& Message) +{ + bool bReturn = false; + NETWORKMODULECALL(OnUserQuitMessage(Message), m_pUser, m_pNetwork, this, &bReturn); + if (!bReturn) { + Close(Csock::CLT_AFTERWRITE); // Treat a client quit as a detach + } + // Don't forward this msg. We don't want the client getting us disconnected. + return true; +} + +bool CClient::OnTextMessage(CTextMessage& Message) +{ + CString sTargets = Message.GetTarget(); + + VCString vTargets; + sTargets.Split(",", vTargets, false); + + for (CString& sTarget : vTargets) { + Message.SetTarget(sTarget); + + if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { + EchoMessage(Message); + if (sTarget.Equals("status")) { + CString sMsg = Message.GetText(); + UserCommand(sMsg); + } else { + CALLMOD(sTarget, this, m_pUser, m_pNetwork, OnModCommand(Message.GetText())); + } + continue; + } + + bool bContinue = false; + NETWORKMODULECALL(OnUserTextMessage(Message), m_pUser, m_pNetwork, this, &bContinue); + if (bContinue) continue; + + if (!GetIRCSock()) { + // Some lagmeters do a PRIVMSG to their own nick, ignore those. + if (!sTarget.Equals(m_sNick)) + PutStatus("Your message to [" + Message.GetTarget() + "] got lost, " + "you are not connected to IRC!"); + continue; + } + + if (m_pNetwork) { + AddBuffer(Message); + EchoMessage(Message); + PutIRC(Message.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); + } + } + + return true; +} + +bool CClient::OnTopicMessage(CTopicMessage& Message) +{ + bool bReturn = false; + CString sChan = Message.GetTarget(); + CString sTopic = Message.GetTopic(); + + if (!sTopic.empty()) { + NETWORKMODULECALL(OnUserTopicMessage(Message), m_pUser, m_pNetwork, this, &bReturn); + } else { + NETWORKMODULECALL(OnUserTopicRequest(sChan), m_pUser, m_pNetwork, this, &bReturn); + Message.SetTarget(sChan); + } + + return bReturn; +} + +bool CClient::OnOtherMessage(CMessage& Message) +{ + const CString& sCommand = Message.GetCommand(); + + if (sCommand.Equals("ZNC")) { + CString sTarget = Message.GetParam(0); + CString sModCommand; + + if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { + sModCommand = Message.GetParams(1); + } else { + sTarget = "status"; + sModCommand = Message.GetParams(0); + } + + if (sTarget.Equals("status")) { + if (sModCommand.empty()) + PutStatus("Hello. How may I help you?"); + else + UserCommand(sModCommand); + } else { + if (sModCommand.empty()) + CALLMOD(sTarget, this, m_pUser, m_pNetwork, PutModule("Hello. How may I help you?")) + else + CALLMOD(sTarget, this, m_pUser, m_pNetwork, OnModCommand(sModCommand)) + } + return true; + } else if (sCommand.Equals("ATTACH")) { + if (!m_pNetwork) { + return true; + } + + CString sPatterns = Message.GetParams(0); + + if (sPatterns.empty()) { + PutStatusNotice("Usage: /attach <#chans|queries>"); + return true; + } + + set sChans = MatchChans(sPatterns); + unsigned int uAttachedChans = AttachChans(sChans); + + PutStatusNotice("There were [" + CString(sChans.size()) + "] channels matching [" + sPatterns + "]"); + PutStatusNotice("Attached [" + CString(uAttachedChans) + "] channels"); + + return true; + } else if (sCommand.Equals("DETACH")) { + if (!m_pNetwork) { + return true; + } + + CString sPatterns = Message.GetParams(0); + + if (sPatterns.empty()) { + PutStatusNotice("Usage: /detach <#chans>"); + return true; + } + + set sChans = MatchChans(sPatterns); + unsigned int uDetached = DetachChans(sChans); + + PutStatusNotice("There were [" + CString(sChans.size()) + "] channels matching [" + sPatterns + "]"); + PutStatusNotice("Detached [" + CString(uDetached) + "] channels"); + + return true; + } else if (sCommand.Equals("PROTOCTL")) { + for (const CString& sParam : Message.GetParams()) { + if (sParam == "NAMESX") { + m_bNamesx = true; + } else if (sParam == "UHNAMES") { + m_bUHNames = true; + } + } + return true; // If the server understands it, we already enabled namesx / uhnames + } + + return false; +}