diff --git a/include/znc/IRCNetwork.h b/include/znc/IRCNetwork.h index a9c3deee..6be95631 100644 --- a/include/znc/IRCNetwork.h +++ b/include/znc/IRCNetwork.h @@ -93,6 +93,9 @@ class CIRCNetwork : private CCoreTranslationMixin { bool AddChan(CChan* pChan); bool AddChan(const CString& sName, bool bInConfig); bool DelChan(const CString& sName); + bool MoveChan(const CString& sChan, unsigned int index, CString& sError); + bool SwapChans(const CString& sChan1, const CString& sChan2, + CString& sError); void JoinChans(); void JoinChans(std::set& sChans); diff --git a/src/ClientCommand.cpp b/src/ClientCommand.cpp index 0809304f..cd768af0 100644 --- a/src/ClientCommand.cpp +++ b/src/ClientCommand.cpp @@ -453,6 +453,46 @@ void CClient::UserCommand(CString& sLine) { PutStatus(t_p("Disabled {1} channel", "Disabled {1} channels", uDisabled)(uDisabled)); } + } else if (sCommand.Equals("MOVECHAN")) { + if (!m_pNetwork) { + PutStatus(t_s( + "You must be connected with a network to use this command")); + return; + } + + const auto sChan = sLine.Token(1); + const auto sTarget = sLine.Token(2); + if (sChan.empty() || sTarget.empty()) { + PutStatus(t_s("Usage: MoveChan <#chan> ")); + return; + } + + unsigned int uIndex = sTarget.ToUInt(); + + CString sError; + if (m_pNetwork->MoveChan(sChan, uIndex - 1, sError)) + PutStatus(t_f("Moved channel {1} to index {2}")(sChan, uIndex)); + else + PutStatus(sError); + } else if (sCommand.Equals("SWAPCHANS")) { + if (!m_pNetwork) { + PutStatus(t_s( + "You must be connected with a network to use this command")); + return; + } + + const auto sChan1 = sLine.Token(1); + const auto sChan2 = sLine.Token(2); + if (sChan1.empty() || sChan2.empty()) { + PutStatus(t_s("Usage: SwapChans <#chan1> <#chan2>")); + return; + } + + CString sError; + if (m_pNetwork->SwapChans(sChan1, sChan2, sError)) + PutStatus(t_f("Swapped channels {1} and {2}")(sChan1, sChan2)); + else + PutStatus(sError); } else if (sCommand.Equals("LISTCHANS")) { if (!m_pNetwork) { PutStatus(t_s( @@ -1786,6 +1826,11 @@ void CClient::HelpUser(const CString& sFilter) { t_s("Enable channels", "helpcmd|EnableChan|desc")); AddCommandHelp("DisableChan", t_s("<#chans>", "helpcmd|DisableChan|args"), t_s("Disable channels", "helpcmd|DisableChan|desc")); + AddCommandHelp("MoveChan", t_s("<#chan> ", "helpcmd|MoveChan|args"), + t_s("Move channel in sort order", "helpcmd|MoveChan|desc")); + AddCommandHelp( + "SwapChans", t_s("<#chan1> <#chan2>", "helpcmd|SwapChans|args"), + t_s("Swap channels in sort order", "helpcmd|SwapChans|desc")); AddCommandHelp("Attach", t_s("<#chans>", "helpcmd|Attach|args"), t_s("Attach to channels", "helpcmd|Attach|desc")); AddCommandHelp("Detach", t_s("<#chans>", "helpcmd|Detach|args"), diff --git a/src/IRCNetwork.cpp b/src/IRCNetwork.cpp index 2855f526..cbe0d538 100644 --- a/src/IRCNetwork.cpp +++ b/src/IRCNetwork.cpp @@ -942,6 +942,49 @@ bool CIRCNetwork::DelChan(const CString& sName) { return false; } +bool CIRCNetwork::MoveChan(const CString& sChan, unsigned int uIndex, + CString& sError) { + if (uIndex >= m_vChans.size()) { + sError = t_s("Invalid index"); + return false; + } + + auto it = m_vChans.begin(); + for (; it != m_vChans.end(); ++it) + if ((*it)->GetName().Equals(sChan)) break; + if (it == m_vChans.end()) { + sError = t_f("You are not on {1}")(sChan); + return false; + } + + const auto pChan = *it; + m_vChans.erase(it); + m_vChans.insert(m_vChans.begin() + uIndex, pChan); + return true; +} + +bool CIRCNetwork::SwapChans(const CString& sChan1, const CString& sChan2, + CString& sError) { + auto it1 = m_vChans.begin(); + for (; it1 != m_vChans.end(); ++it1) + if ((*it1)->GetName().Equals(sChan1)) break; + if (it1 == m_vChans.end()) { + sError = t_f("You are not on {1}")(sChan1); + return false; + } + + auto it2 = m_vChans.begin(); + for (; it2 != m_vChans.end(); ++it2) + if ((*it2)->GetName().Equals(sChan2)) break; + if (it2 == m_vChans.end()) { + sError = t_f("You are not on {1}")(sChan2); + return false; + } + + std::swap(*it1, *it2); + return true; +} + void CIRCNetwork::JoinChans() { // Avoid divsion by zero, it's bad! if (m_vChans.empty()) return; diff --git a/test/integration/tests/core.cpp b/test/integration/tests/core.cpp index f50a32c7..c5531d05 100644 --- a/test/integration/tests/core.cpp +++ b/test/integration/tests/core.cpp @@ -307,5 +307,41 @@ TEST_F(ZNCTest, StatusEchoMessage) { client3.ReadUntil(":*status!znc@znc.in PRIVMSG nick :Unknown command"); } +TEST_F(ZNCTest, MoveChannels) { + auto znc = Run(); + auto ircd = ConnectIRCd(); + + auto client = LoginClient(); + client.Write("JOIN #foo,#bar"); + client.Close(); + + ircd.Write(":server 001 nick :Hello"); + ircd.ReadUntil("JOIN #foo,#bar"); + ircd.Write(":nick JOIN :#foo"); + ircd.Write(":server 353 nick #foo :nick"); + ircd.Write(":server 366 nick #foo :End of /NAMES list"); + ircd.Write(":nick JOIN :#bar"); + ircd.Write(":server 353 nick #bar :nick"); + ircd.Write(":server 366 nick #bar :End of /NAMES list"); + + client = LoginClient(); + client.ReadUntil(":nick JOIN :#foo"); + client.ReadUntil(":nick JOIN :#bar"); + client.Write("znc movechan #foo 2"); + client.ReadUntil("Moved channel #foo to index 2"); + client.Close(); + + client = LoginClient(); + client.ReadUntil(":nick JOIN :#bar"); + client.ReadUntil(":nick JOIN :#foo"); + client.Write("znc swapchans #foo #bar"); + client.ReadUntil("Swapped channels #foo and #bar"); + client.Close(); + + client = LoginClient(); + client.ReadUntil(":nick JOIN :#foo"); + client.ReadUntil(":nick JOIN :#bar"); +} + } // namespace } // namespace znc_inttest