/*
 * Copyright (C) 2004-2025 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 <znc/IRCSock.h>
#include <znc/Modules.h>
#include <znc/FileUtils.h>
#include <znc/Template.h>
#include <znc/User.h>
#include <znc/Query.h>
#include <znc/IRCNetwork.h>
#include <znc/WebModules.h>
#include <znc/znc.h>
#include <dlfcn.h>

using std::map;
using std::set;
using std::vector;

bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER;

#ifndef RTLD_LOCAL
#define RTLD_LOCAL 0
#warning "your crap box doesn't define RTLD_LOCAL !?"
#endif

#define MODUNLOADCHK(func)                              \
    for (CModule * pMod : *this) {                      \
        try {                                           \
            CClient* pOldClient = pMod->GetClient();    \
            pMod->SetClient(m_pClient);                 \
            CUser* pOldUser = nullptr;                  \
            if (m_pUser) {                              \
                pOldUser = pMod->GetUser();             \
                pMod->SetUser(m_pUser);                 \
            }                                           \
            CIRCNetwork* pNetwork = nullptr;            \
            if (m_pNetwork) {                           \
                pNetwork = pMod->GetNetwork();          \
                pMod->SetNetwork(m_pNetwork);           \
            }                                           \
            pMod->func;                                 \
            if (m_pUser) pMod->SetUser(pOldUser);       \
            if (m_pNetwork) pMod->SetNetwork(pNetwork); \
            pMod->SetClient(pOldClient);                \
        } catch (const CModule::EModException& e) {     \
            if (e == CModule::UNLOAD) {                 \
                UnloadModule(pMod->GetModName());       \
            }                                           \
        }                                               \
    }

#define MODHALTCHK(func)                                \
    bool bHaltCore = false;                             \
    for (CModule * pMod : *this) {                      \
        try {                                           \
            CModule::EModRet e = CModule::CONTINUE;     \
            CClient* pOldClient = pMod->GetClient();    \
            pMod->SetClient(m_pClient);                 \
            CUser* pOldUser = nullptr;                  \
            if (m_pUser) {                              \
                pOldUser = pMod->GetUser();             \
                pMod->SetUser(m_pUser);                 \
            }                                           \
            CIRCNetwork* pNetwork = nullptr;            \
            if (m_pNetwork) {                           \
                pNetwork = pMod->GetNetwork();          \
                pMod->SetNetwork(m_pNetwork);           \
            }                                           \
            e = pMod->func;                             \
            if (m_pUser) pMod->SetUser(pOldUser);       \
            if (m_pNetwork) pMod->SetNetwork(pNetwork); \
            pMod->SetClient(pOldClient);                \
            if (e == CModule::HALTMODS) {               \
                break;                                  \
            } else if (e == CModule::HALTCORE) {        \
                bHaltCore = true;                       \
            } else if (e == CModule::HALT) {            \
                bHaltCore = true;                       \
                break;                                  \
            }                                           \
        } catch (const CModule::EModException& e) {     \
            if (e == CModule::UNLOAD) {                 \
                UnloadModule(pMod->GetModName());       \
            }                                           \
        }                                               \
    }                                                   \
    return bHaltCore;

/////////////////// Timer ///////////////////
CTimer::CTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles,
               const CString& sLabel, const CString& sDescription)
    : CCron(), m_pModule(pModule), m_sDescription(sDescription) {
    SetName(sLabel);

    // Make integration test faster
    char* szDebugTimer = getenv("ZNC_DEBUG_TIMER");
    if (szDebugTimer && *szDebugTimer == '1') {
        uInterval = std::max(1u, uInterval / 4u);
    }

    if (uCycles) {
        StartMaxCycles(uInterval, uCycles);
    } else {
        Start(uInterval);
    }
}

CTimer::~CTimer() { m_pModule->UnlinkTimer(this); }

void CTimer::SetModule(CModule* p) { m_pModule = p; }
void CTimer::SetDescription(const CString& s) { m_sDescription = s; }
CModule* CTimer::GetModule() const { return m_pModule; }
const CString& CTimer::GetDescription() const { return m_sDescription; }
/////////////////// !Timer ///////////////////

CModule::CModule(ModHandle pDLL, CUser* pUser, CIRCNetwork* pNetwork,
                 const CString& sModName, const CString& sDataDir,
                 CModInfo::EModuleType eType)
    : m_eType(eType),
      m_sDescription(""),
      m_sTimers(),
      m_sSockets(),
#ifdef HAVE_PTHREAD
      m_sJobs(),
#endif
      m_pDLL(pDLL),
      m_pManager(&(CZNC::Get().GetManager())),
      m_pUser(pUser),
      m_pNetwork(pNetwork),
      m_pClient(nullptr),
      m_sModName(sModName),
      m_sDataDir(sDataDir),
      m_sSavePath(""),
      m_sArgs(""),
      m_sModPath(""),
      m_Translation("znc-" + sModName),
      m_mssRegistry(),
      m_vSubPages(),
      m_mCommands() {
    if (m_pNetwork) {
        m_sSavePath = m_pNetwork->GetNetworkPath() + "/moddata/" + m_sModName;
    } else if (m_pUser) {
        m_sSavePath = m_pUser->GetUserPath() + "/moddata/" + m_sModName;
    } else {
        m_sSavePath = CZNC::Get().GetZNCPath() + "/moddata/" + m_sModName;
    }
    LoadRegistry();
}

CModule::~CModule() {
    for (const auto& [sName, pCap] : m_mServerDependentCaps) {
        // pCap->OnClientChangedSupport is useless (and even dangerous) to call
        // from the destructor, since the derived CModule class is gone already.
        // But still need to tell clients via cap-notify that the cap is gone.
        switch (GetType()) {
            case CModInfo::NetworkModule:
                GetNetwork()->NotifyClientsAboutServerDependentCap(sName,
                                                                   false);
                break;
            case CModInfo::UserModule:
                for (CIRCNetwork* pNetwork : GetUser()->GetNetworks()) {
                    pNetwork->NotifyClientsAboutServerDependentCap(sName,
                                                                   false);
                }
                break;
            case CModInfo::GlobalModule:
                for (auto& [_, pUser] : CZNC::Get().GetUserMap()) {
                    for (CIRCNetwork* pNetwork : pUser->GetNetworks()) {
                        pNetwork->NotifyClientsAboutServerDependentCap(sName,
                                                                       false);
                    }
                }
        }
    }

    while (!m_sTimers.empty()) {
        RemTimer(*m_sTimers.begin());
    }

    while (!m_sSockets.empty()) {
        RemSocket(*m_sSockets.begin());
    }

    SaveRegistry();

#ifdef HAVE_PTHREAD
    CancelJobs(m_sJobs);
#endif
}

void CModule::SetUser(CUser* pUser) { m_pUser = pUser; }
void CModule::SetNetwork(CIRCNetwork* pNetwork) { m_pNetwork = pNetwork; }
void CModule::SetClient(CClient* pClient) { m_pClient = pClient; }

CString CModule::ExpandString(const CString& sStr) const {
    CString sRet;
    return ExpandString(sStr, sRet);
}

CString& CModule::ExpandString(const CString& sStr, CString& sRet) const {
    sRet = sStr;

    if (m_pNetwork) {
        return m_pNetwork->ExpandString(sRet, sRet);
    }

    if (m_pUser) {
        return m_pUser->ExpandString(sRet, sRet);
    }

    return sRet;
}

const CString& CModule::GetSavePath() const {
    if (!CFile::Exists(m_sSavePath)) {
        CDir::MakeDir(m_sSavePath);
    }
    return m_sSavePath;
}

CString CModule::GetWebPath() {
    switch (m_eType) {
        case CModInfo::GlobalModule:
            return "/mods/global/" + GetModName() + "/";
        case CModInfo::UserModule:
            return "/mods/user/" + GetModName() + "/";
        case CModInfo::NetworkModule:
            return "/mods/network/" + m_pNetwork->GetName() + "/" +
                   GetModName() + "/";
        default:
            return "/";
    }
}

CString CModule::GetWebFilesPath() {
    switch (m_eType) {
        case CModInfo::GlobalModule:
            return "/modfiles/global/" + GetModName() + "/";
        case CModInfo::UserModule:
            return "/modfiles/user/" + GetModName() + "/";
        case CModInfo::NetworkModule:
            return "/modfiles/network/" + m_pNetwork->GetName() + "/" +
                   GetModName() + "/";
        default:
            return "/";
    }
}

bool CModule::LoadRegistry() {
    // CString sPrefix = (m_pUser) ? m_pUser->GetUsername() : ".global";
    return (m_mssRegistry.ReadFromDisk(GetSavePath() + "/.registry") ==
            MCString::MCS_SUCCESS);
}

bool CModule::SaveRegistry() const {
    // CString sPrefix = (m_pUser) ? m_pUser->GetUsername() : ".global";
    return (m_mssRegistry.WriteToDisk(GetSavePath() + "/.registry", 0600) ==
            MCString::MCS_SUCCESS);
}

bool CModule::MoveRegistry(const CString& sPath) {
    if (m_sSavePath != sPath) {
        CFile fOldNVFile = CFile(m_sSavePath + "/.registry");
        if (!fOldNVFile.Exists()) {
            return false;
        }
        if (!CFile::Exists(sPath) && !CDir::MakeDir(sPath)) {
            return false;
        }
        fOldNVFile.Copy(sPath + "/.registry");
        m_sSavePath = sPath;
        return true;
    }
    return false;
}

bool CModule::SetNV(const CString& sName, const CString& sValue,
                    bool bWriteToDisk) {
    m_mssRegistry[sName] = sValue;
    if (bWriteToDisk) {
        return SaveRegistry();
    }

    return true;
}

CString CModule::GetNV(const CString& sName) const {
    MCString::const_iterator it = m_mssRegistry.find(sName);

    if (it != m_mssRegistry.end()) {
        return it->second;
    }

    return "";
}

bool CModule::DelNV(const CString& sName, bool bWriteToDisk) {
    MCString::iterator it = m_mssRegistry.find(sName);

    if (it != m_mssRegistry.end()) {
        m_mssRegistry.erase(it);
    } else {
        return false;
    }

    if (bWriteToDisk) {
        return SaveRegistry();
    }

    return true;
}

bool CModule::ClearNV(bool bWriteToDisk) {
    m_mssRegistry.clear();

    if (bWriteToDisk) {
        return SaveRegistry();
    }
    return true;
}

bool CModule::AddTimer(CTimer* pTimer) {
    if ((!pTimer) ||
        (!pTimer->GetName().empty() && FindTimer(pTimer->GetName()))) {
        delete pTimer;
        return false;
    }

    if (!m_sTimers.insert(pTimer).second)
        // Was already added
        return true;

    m_pManager->AddCron(pTimer);
    return true;
}

bool CModule::AddTimer(FPTimer_t pFBCallback, const CString& sLabel,
                       u_int uInterval, u_int uCycles,
                       const CString& sDescription) {
    CFPTimer* pTimer =
        new CFPTimer(this, uInterval, uCycles, sLabel, sDescription);
    pTimer->SetFPCallback(pFBCallback);

    return AddTimer(pTimer);
}

bool CModule::RemTimer(CTimer* pTimer) {
    if (m_sTimers.erase(pTimer) == 0) return false;
    m_pManager->DelCronByAddr(pTimer);
    return true;
}

bool CModule::RemTimer(const CString& sLabel) {
    CTimer* pTimer = FindTimer(sLabel);
    if (!pTimer) return false;
    return RemTimer(pTimer);
}

bool CModule::UnlinkTimer(CTimer* pTimer) { return m_sTimers.erase(pTimer); }

CTimer* CModule::FindTimer(const CString& sLabel) {
    if (sLabel.empty()) {
        return nullptr;
    }

    for (CTimer* pTimer : m_sTimers) {
        if (pTimer->GetName().Equals(sLabel)) {
            return pTimer;
        }
    }

    return nullptr;
}

void CModule::ListTimers() {
    if (m_sTimers.empty()) {
        PutModule("You have no timers running.");
        return;
    }

    CTable Table;
    Table.AddColumn("Name");
    Table.AddColumn("Secs");
    Table.AddColumn("Cycles");
    Table.AddColumn("Description");

    for (const CTimer* pTimer : m_sTimers) {
        unsigned int uCycles = pTimer->GetCyclesLeft();
        timeval Interval = pTimer->GetInterval();

        Table.AddRow();
        Table.SetCell("Name", pTimer->GetName());
        Table.SetCell(
            "Secs", CString(Interval.tv_sec) + "seconds" +
                        (Interval.tv_usec
                             ? " " + CString(Interval.tv_usec) + " microseconds"
                             : ""));
        Table.SetCell("Cycles", ((uCycles) ? CString(uCycles) : "INF"));
        Table.SetCell("Description", pTimer->GetDescription());
    }

    PutModule(Table);
}

bool CModule::AddSocket(CSocket* pSocket) {
    if (!pSocket) {
        return false;
    }

    m_sSockets.insert(pSocket);
    return true;
}

bool CModule::RemSocket(CSocket* pSocket) {
    if (m_sSockets.erase(pSocket)) {
        m_pManager->DelSockByAddr(pSocket);
        return true;
    }

    return false;
}

bool CModule::RemSocket(const CString& sSockName) {
    for (CSocket* pSocket : m_sSockets) {
        if (pSocket->GetSockName().Equals(sSockName)) {
            m_sSockets.erase(pSocket);
            m_pManager->DelSockByAddr(pSocket);
            return true;
        }
    }

    return false;
}

bool CModule::UnlinkSocket(CSocket* pSocket) {
    return m_sSockets.erase(pSocket);
}

CSocket* CModule::FindSocket(const CString& sSockName) {
    for (CSocket* pSocket : m_sSockets) {
        if (pSocket->GetSockName().Equals(sSockName)) {
            return pSocket;
        }
    }

    return nullptr;
}

void CModule::ListSockets() {
    if (m_sSockets.empty()) {
        PutModule("You have no open sockets.");
        return;
    }

    CTable Table;
    Table.AddColumn("Name");
    Table.AddColumn("State");
    Table.AddColumn("LocalPort");
    Table.AddColumn("SSL");
    Table.AddColumn("RemoteIP");
    Table.AddColumn("RemotePort");

    for (const CSocket* pSocket : m_sSockets) {
        Table.AddRow();
        Table.SetCell("Name", pSocket->GetSockName());

        if (pSocket->GetType() == CSocket::LISTENER) {
            Table.SetCell("State", "Listening");
        } else {
            Table.SetCell("State", (pSocket->IsConnected() ? "Connected" : ""));
        }

        Table.SetCell("LocalPort", CString(pSocket->GetLocalPort()));
        Table.SetCell("SSL", (pSocket->GetSSL() ? "yes" : "no"));
        Table.SetCell("RemoteIP", pSocket->GetRemoteIP());
        Table.SetCell("RemotePort", (pSocket->GetRemotePort())
                                        ? CString(pSocket->GetRemotePort())
                                        : CString(""));
    }

    PutModule(Table);
}

#ifdef HAVE_PTHREAD
CModuleJob::~CModuleJob() { m_pModule->UnlinkJob(this); }

void CModule::AddJob(CModuleJob* pJob) {
    CThreadPool::Get().addJob(pJob);
    m_sJobs.insert(pJob);
}

void CModule::CancelJob(CModuleJob* pJob) {
    if (pJob == nullptr) return;
    // Destructor calls UnlinkJob and removes the job from m_sJobs
    CThreadPool::Get().cancelJob(pJob);
}

bool CModule::CancelJob(const CString& sJobName) {
    for (CModuleJob* pJob : m_sJobs) {
        if (pJob->GetName().Equals(sJobName)) {
            CancelJob(pJob);
            return true;
        }
    }
    return false;
}

void CModule::CancelJobs(const std::set<CModuleJob*>& sJobs) {
    set<CJob*> sPlainJobs(sJobs.begin(), sJobs.end());

    // Destructor calls UnlinkJob and removes the jobs from m_sJobs
    CThreadPool::Get().cancelJobs(sPlainJobs);
}

bool CModule::UnlinkJob(CModuleJob* pJob) { return 0 != m_sJobs.erase(pJob); }
#endif

bool CModule::AddCommand(const CModCommand& Command) {
    if (Command.GetFunction() == nullptr) return false;
    if (Command.GetCommand().Contains(" ")) return false;
    if (FindCommand(Command.GetCommand()) != nullptr) return false;

    m_mCommands[Command.GetCommand()] = Command;
    return true;
}

bool CModule::AddCommand(const CString& sCmd, CModCommand::ModCmdFunc func,
                         const CString& sArgs, const CString& sDesc) {
    CModCommand cmd(sCmd, this, func, sArgs, sDesc);
    return AddCommand(cmd);
}

bool CModule::AddCommand(const CString& sCmd, const COptionalTranslation& Args,
                         const COptionalTranslation& Desc,
                         std::function<void(const CString& sLine)> func) {
    CModCommand cmd(sCmd, std::move(func), Args, Desc);
    return AddCommand(std::move(cmd));
}

void CModule::AddHelpCommand() {
    AddCommand("Help", t_d("<search>", "modhelpcmd"),
               t_d("Generate this output", "modhelpcmd"),
               [=](const CString& sLine) { HandleHelpCommand(sLine); });
}

bool CModule::RemCommand(const CString& sCmd) {
    return m_mCommands.erase(sCmd) > 0;
}

const CModCommand* CModule::FindCommand(const CString& sCmd) const {
    for (const auto& it : m_mCommands) {
        if (!it.first.Equals(sCmd)) continue;
        return &it.second;
    }
    return nullptr;
}

bool CModule::HandleCommand(const CString& sLine) {
    const CString& sCmd = sLine.Token(0);
    const CModCommand* pCmd = FindCommand(sCmd);

    if (pCmd) {
        pCmd->Call(sLine);
        return true;
    }

    OnUnknownModCommand(sLine);

    return false;
}

void CModule::HandleHelpCommand(const CString& sLine) {
    CString sFilter = sLine.Token(1).AsLower();
    CTable Table;

    CModCommand::InitHelp(Table);
    for (const auto& it : m_mCommands) {
        CString sCmd = it.second.GetCommand().AsLower();
        if (sFilter.empty() ||
            (sCmd.StartsWith(sFilter, CString::CaseSensitive)) ||
            sCmd.WildCmp(sFilter)) {
            it.second.AddHelp(Table);
        }
    }
    if (Table.empty()) {
        PutModule(t_f("No matches for '{1}'")(sFilter));
    } else {
        PutModule(Table);
    }
}

CString CModule::GetModNick() const {
    return ((m_pUser) ? m_pUser->GetStatusPrefix() : "*") + m_sModName;
}

// Webmods
bool CModule::OnWebPreRequest(CWebSock& WebSock, const CString& sPageName) {
    return false;
}
bool CModule::OnWebRequest(CWebSock& WebSock, const CString& sPageName,
                           CTemplate& Tmpl) {
    return false;
}
bool CModule::ValidateWebRequestCSRFCheck(CWebSock& WebSock,
    const CString& sPageName) {
    return WebSock.ValidateCSRFCheck(WebSock.GetURI());
}
bool CModule::OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName,
                                   CTemplate& Tmpl) {
    return false;
}
// !Webmods

bool CModule::OnLoad(const CString& sArgs, CString& sMessage) {
    sMessage = "";
    return true;
}
bool CModule::OnBoot() { return true; }
void CModule::OnPreRehash() {}
void CModule::OnPostRehash() {}
void CModule::OnIRCDisconnected() {}
void CModule::InternalServerDependentCapsOnIRCDisconnected() {
    OnIRCDisconnected();
    for (const auto& [sName, pCap] : m_mServerDependentCaps) {
        GetNetwork()->NotifyClientsAboutServerDependentCap(sName, false);
        for (CClient* pClient : GetNetwork()->GetClients()) {
            pCap->OnClientChangedSupport(pClient, false);
        }
    }
}
void CModule::OnIRCConnected() {}
void CModule::InternalServerDependentCapsOnIRCConnected() {
    OnIRCConnected();
    for (const auto& [sName, pCap] : m_mServerDependentCaps) {
        if (GetNetwork()->IsServerCapAccepted(sName)) {
            GetNetwork()->NotifyClientsAboutServerDependentCap(sName, true);
        }
    }
}
CModule::EModRet CModule::OnIRCConnecting(CIRCSock* IRCSock) {
    return CONTINUE;
}
void CModule::OnIRCConnectionError(CIRCSock* IRCSock) {}
CModule::EModRet CModule::OnIRCRegistration(CString& sPass, CString& sNick,
                                            CString& sIdent,
                                            CString& sRealName) {
    return CONTINUE;
}
CModule::EModRet CModule::OnBroadcast(CString& sMessage) { return CONTINUE; }

void CModule::OnChanPermission3(const CNick* pOpNick, const CNick& Nick,
                                CChan& Channel, char cMode,
                                bool bAdded, bool bNoChange) {
    OnChanPermission2(pOpNick, Nick, Channel, cMode, bAdded, bNoChange);
}
void CModule::OnChanPermission2(const CNick* pOpNick, const CNick& Nick,
                                CChan& Channel, unsigned char uMode,
                                bool bAdded, bool bNoChange) {
    if (pOpNick)
        OnChanPermission(*pOpNick, Nick, Channel, uMode, bAdded, bNoChange);
}
void CModule::OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel,
                    bool bNoChange) {
    if (pOpNick) OnOp(*pOpNick, Nick, Channel, bNoChange);
}
void CModule::OnDeop2(const CNick* pOpNick, const CNick& Nick, CChan& Channel,
                      bool bNoChange) {
    if (pOpNick) OnDeop(*pOpNick, Nick, Channel, bNoChange);
}
void CModule::OnVoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel,
                       bool bNoChange) {
    if (pOpNick) OnVoice(*pOpNick, Nick, Channel, bNoChange);
}
void CModule::OnDevoice2(const CNick* pOpNick, const CNick& Nick,
                         CChan& Channel, bool bNoChange) {
    if (pOpNick) OnDevoice(*pOpNick, Nick, Channel, bNoChange);
}
void CModule::OnRawMode2(const CNick* pOpNick, CChan& Channel,
                         const CString& sModes, const CString& sArgs) {
    if (pOpNick) OnRawMode(*pOpNick, Channel, sModes, sArgs);
}
void CModule::OnMode2(const CNick* pOpNick, CChan& Channel, char uMode,
                      const CString& sArg, bool bAdded, bool bNoChange) {
    if (pOpNick) OnMode(*pOpNick, Channel, uMode, sArg, bAdded, bNoChange);
}

void CModule::OnChanPermission(const CNick& pOpNick, const CNick& Nick,
                               CChan& Channel, unsigned char uMode, bool bAdded,
                               bool bNoChange) {}
void CModule::OnOp(const CNick& pOpNick, const CNick& Nick, CChan& Channel,
                   bool bNoChange) {}
void CModule::OnDeop(const CNick& pOpNick, const CNick& Nick, CChan& Channel,
                     bool bNoChange) {}
void CModule::OnVoice(const CNick& pOpNick, const CNick& Nick, CChan& Channel,
                      bool bNoChange) {}
void CModule::OnDevoice(const CNick& pOpNick, const CNick& Nick, CChan& Channel,
                        bool bNoChange) {}
void CModule::OnRawMode(const CNick& pOpNick, CChan& Channel,
                        const CString& sModes, const CString& sArgs) {}
void CModule::OnMode(const CNick& pOpNick, CChan& Channel, char uMode,
                     const CString& sArg, bool bAdded, bool bNoChange) {}

CModule::EModRet CModule::OnRaw(CString& sLine) { return CONTINUE; }
CModule::EModRet CModule::OnRawMessage(CMessage& Message) { return CONTINUE; }
CModule::EModRet CModule::OnNumericMessage(CNumericMessage& Message) {
    return CONTINUE;
}

CModule::EModRet CModule::OnStatusCommand(CString& sCommand) {
    return CONTINUE;
}
void CModule::OnModNotice(const CString& sMessage) {}
void CModule::OnModCTCP(const CString& sMessage) {}

void CModule::OnModCommand(const CString& sCommand) { HandleCommand(sCommand); }
void CModule::OnUnknownModCommand(const CString& sLine) {
    if (m_mCommands.empty())
        // This function is only called if OnModCommand wasn't
        // overriden, so no false warnings for modules which don't use
        // CModCommand for command handling.
        PutModule(t_s("This module doesn't implement any commands."));
    else
        PutModule(t_s("Unknown command!"));
}

void CModule::OnQuit(const CNick& Nick, const CString& sMessage,
                     const vector<CChan*>& vChans) {}
void CModule::OnQuitMessage(CQuitMessage& Message,
                            const vector<CChan*>& vChans) {
    OnQuit(Message.GetNick(), Message.GetReason(), vChans);
}
void CModule::OnNick(const CNick& Nick, const CString& sNewNick,
                     const vector<CChan*>& vChans) {}
void CModule::OnNickMessage(CNickMessage& Message,
                            const vector<CChan*>& 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;
}
CModule::EModRet CModule::OnChanBufferEnding(CChan& Chan, CClient& Client) {
    return CONTINUE;
}
CModule::EModRet CModule::OnChanBufferPlayLine(CChan& Chan, CClient& Client,
                                               CString& sLine) {
    return CONTINUE;
}
CModule::EModRet CModule::OnPrivBufferStarting(CQuery& Query, CClient& Client) {
    return CONTINUE;
}
CModule::EModRet CModule::OnPrivBufferEnding(CQuery& Query, CClient& Client) {
    return CONTINUE;
}
CModule::EModRet CModule::OnPrivBufferPlayLine(CClient& Client,
                                               CString& sLine) {
    return CONTINUE;
}

CModule::EModRet CModule::OnChanBufferPlayLine2(CChan& Chan, CClient& Client,
                                                CString& sLine,
                                                const timeval& tv) {
    return OnChanBufferPlayLine(Chan, Client, sLine);
}
CModule::EModRet CModule::OnPrivBufferPlayLine2(CClient& Client, CString& sLine,
                                                const timeval& tv) {
    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 (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 (sOriginal != sModified) {
        Message.Parse(sModified);
    }
    return ret;
}

void CModule::OnClientLogin() {}
void CModule::OnClientDisconnect() {}
CModule::EModRet CModule::OnUserRaw(CString& sLine) { return CONTINUE; }
CModule::EModRet CModule::OnUserRawMessage(CMessage& Message) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserCTCPReply(CString& sTarget, CString& sMessage) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserCTCPReplyMessage(CCTCPMessage& Message) {
    CString sTarget = Message.GetTarget();
    CString sText = Message.GetText();
    EModRet ret = OnUserCTCPReply(sTarget, sText);
    Message.SetTarget(sTarget);
    Message.SetText(sText);
    return ret;
}
CModule::EModRet CModule::OnUserCTCP(CString& sTarget, CString& sMessage) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserCTCPMessage(CCTCPMessage& Message) {
    CString sTarget = Message.GetTarget();
    CString sText = Message.GetText();
    EModRet ret = OnUserCTCP(sTarget, sText);
    Message.SetTarget(sTarget);
    Message.SetText(sText);
    return ret;
}
CModule::EModRet CModule::OnUserAction(CString& sTarget, CString& sMessage) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserActionMessage(CActionMessage& Message) {
    CString sTarget = Message.GetTarget();
    CString sText = Message.GetText();
    EModRet ret = OnUserAction(sTarget, sText);
    Message.SetTarget(sTarget);
    Message.SetText(sText);
    return ret;
}
CModule::EModRet CModule::OnUserMsg(CString& sTarget, CString& sMessage) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserTextMessage(CTextMessage& Message) {
    CString sTarget = Message.GetTarget();
    CString sText = Message.GetText();
    EModRet ret = OnUserMsg(sTarget, sText);
    Message.SetTarget(sTarget);
    Message.SetText(sText);
    return ret;
}
CModule::EModRet CModule::OnUserNotice(CString& sTarget, CString& sMessage) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserNoticeMessage(CNoticeMessage& Message) {
    CString sTarget = Message.GetTarget();
    CString sText = Message.GetText();
    EModRet ret = OnUserNotice(sTarget, sText);
    Message.SetTarget(sTarget);
    Message.SetText(sText);
    return ret;
}
CModule::EModRet CModule::OnUserTagMessage(CTargetMessage& Message) {
    return CONTINUE;
}
CModule::EModRet CModule::OnPrivTagMessage(CTargetMessage& Message) {
    return CONTINUE;
}
CModule::EModRet CModule::OnChanTagMessage(CTargetMessage& Message) {
    return CONTINUE;
}
CModule::EModRet CModule::OnInviteMessage(CInviteMessage& Message) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserJoin(CString& sChannel, CString& sKey) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserJoinMessage(CJoinMessage& Message) {
    CString sChan = Message.GetTarget();
    CString sKey = Message.GetKey();
    EModRet ret = OnUserJoin(sChan, sKey);
    Message.SetTarget(sChan);
    Message.SetKey(sKey);
    return ret;
}
CModule::EModRet CModule::OnUserPart(CString& sChannel, CString& sMessage) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserPartMessage(CPartMessage& Message) {
    CString sChan = Message.GetTarget();
    CString sReason = Message.GetReason();
    EModRet ret = OnUserPart(sChan, sReason);
    Message.SetTarget(sChan);
    Message.SetReason(sReason);
    return ret;
}
CModule::EModRet CModule::OnUserTopic(CString& sChannel, CString& sTopic) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserTopicMessage(CTopicMessage& Message) {
    CString sChan = Message.GetTarget();
    CString sTopic = Message.GetTopic();
    EModRet ret = OnUserTopic(sChan, sTopic);
    Message.SetTarget(sChan);
    Message.SetTopic(sTopic);
    return ret;
}
CModule::EModRet CModule::OnUserTopicRequest(CString& sChannel) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUserQuit(CString& sMessage) { return CONTINUE; }
CModule::EModRet CModule::OnUserQuitMessage(CQuitMessage& Message) {
    CString sReason = Message.GetReason();
    EModRet ret = OnUserQuit(sReason);
    Message.SetReason(sReason);
    return ret;
}

CModule::EModRet CModule::OnCTCPReply(CNick& Nick, CString& sMessage) {
    return CONTINUE;
}
CModule::EModRet CModule::OnCTCPReplyMessage(CCTCPMessage& Message) {
    CString sText = Message.GetText();
    EModRet ret = OnCTCPReply(Message.GetNick(), sText);
    Message.SetText(sText);
    return ret;
}
CModule::EModRet CModule::OnPrivCTCP(CNick& Nick, CString& sMessage) {
    return CONTINUE;
}
CModule::EModRet CModule::OnPrivCTCPMessage(CCTCPMessage& 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(CCTCPMessage& 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(CActionMessage& 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(CActionMessage& 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::OnPrivTextMessage(CTextMessage& 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::OnChanTextMessage(CTextMessage& 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(CNoticeMessage& 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(CNoticeMessage& 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;
}

CModule::EModRet CModule::OnSendToClient(CString& sLine, CClient& Client) {
    return CONTINUE;
}
CModule::EModRet CModule::OnSendToClientMessage(CMessage& Message) {
    return CONTINUE;
}

CModule::EModRet CModule::OnSendToIRC(CString& sLine) { return CONTINUE; }
CModule::EModRet CModule::OnSendToIRCMessage(CMessage& Message) {
    return CONTINUE;
}
void CModule::OnClientAttached() {}
void CModule::InternalServerDependentCapsOnClientAttached() {
    OnClientAttached();
    if (!GetNetwork()) return;
    for (const auto& [sName, pCap] : m_mServerDependentCaps) {
        if (GetNetwork()->IsServerCapAccepted(sName)) {
            GetClient()->NotifyServerDependentCap(sName, true, GetNetwork()->GetIRCSock()->GetCapLsValue(sName));
        }
    }
}
void CModule::OnClientDetached() {}
void CModule::InternalServerDependentCapsOnClientDetached() {
    OnClientDetached();
    for (const auto& [sName, pCap] : m_mServerDependentCaps) {
        GetClient()->NotifyServerDependentCap(sName, false, "");
        pCap->OnClientChangedSupport(GetClient(), false);
    }
}

bool CModule::OnServerCapAvailable(const CString& sCap) { return false; }
bool CModule::OnServerCap302Available(const CString& sCap,
                                      const CString& sValue) {
    return OnServerCapAvailable(sCap);
}
bool CModule::InternalServerDependentCapsOnServerCap302Available(const CString& sCap,
                                      const CString& sValue) {
    auto it = m_mServerDependentCaps.find(sCap);
    if (it == m_mServerDependentCaps.end())
        return OnServerCap302Available(sCap, sValue);
    if (GetNetwork()->IsServerCapAccepted(sCap)) {
        // This can happen when server sent CAP NEW with another value.
        GetNetwork()->NotifyClientsAboutServerDependentCap(sCap, true);
        // It's enabled already, no need to REQ it again.
        return false;
    }
    return true;
}
void CModule::OnServerCapResult(const CString& sCap, bool bSuccess) {}
void CModule::InternalServerDependentCapsOnServerCapResult(const CString& sCap,
                                                           bool bSuccess) {
    OnServerCapResult(sCap, bSuccess);
    auto it = m_mServerDependentCaps.find(sCap);
    if (it == m_mServerDependentCaps.end()) return;
    it->second->OnServerChangedSupport(GetNetwork(), bSuccess);
    if (GetNetwork()->GetIRCSock()->IsAuthed()) {
        GetNetwork()->NotifyClientsAboutServerDependentCap(sCap, bSuccess);
        if (!bSuccess) {
            for (CClient* pClient : GetNetwork()->GetClients()) {
                it->second->OnClientChangedSupport(pClient, false);
            }
        }
    }
}

bool CModule::PutIRC(const CString& sLine) {
    return m_pNetwork ? m_pNetwork->PutIRC(sLine) : false;
}
bool CModule::PutIRC(const CMessage& Message) {
    return m_pNetwork ? m_pNetwork->PutIRC(Message) : false;
}
bool CModule::PutUser(const CString& sLine) {
    return m_pNetwork ? m_pNetwork->PutUser(sLine, m_pClient) : false;
}
bool CModule::PutStatus(const CString& sLine) {
    return m_pNetwork ? m_pNetwork->PutStatus(sLine, m_pClient) : false;
}
unsigned int CModule::PutModule(const CTable& table) {
    if (!m_pUser) return 0;

    unsigned int idx = 0;
    CString sLine;
    while (table.GetLine(idx++, sLine)) PutModule(sLine);
    return idx - 1;
}
bool CModule::PutModule(const CString& sLine) {
    if (m_pClient) {
        m_pClient->PutModule(GetModName(), sLine);
        return true;
    }

    if (m_pNetwork) {
        return m_pNetwork->PutModule(GetModName(), sLine);
    }

    if (m_pUser) {
        return m_pUser->PutModule(GetModName(), sLine);
    }

    return false;
}
bool CModule::PutModNotice(const CString& sLine) {
    if (!m_pUser) return false;

    if (m_pClient) {
        m_pClient->PutModNotice(GetModName(), sLine);
        return true;
    }

    return m_pUser->PutModNotice(GetModName(), sLine);
}

///////////////////
// Global Module //
///////////////////
CModule::EModRet CModule::OnAddUser(CUser& User, CString& sErrorRet) {
    return CONTINUE;
}
CModule::EModRet CModule::OnDeleteUser(CUser& User) { return CONTINUE; }
void CModule::OnClientConnect(CZNCSock* pClient, const CString& sHost,
                              unsigned short uPort) {}
CModule::EModRet CModule::OnLoginAttempt(std::shared_ptr<CAuthBase> Auth) {
    return CONTINUE;
}
void CModule::OnFailedLogin(const CString& sUsername,
                            const CString& sRemoteIP) {}
CModule::EModRet CModule::OnUnknownUserRaw(CClient* pClient, CString& sLine) {
    return CONTINUE;
}
CModule::EModRet CModule::OnUnknownUserRawMessage(CMessage& Message) {
    return CONTINUE;
}
void CModule::OnClientCapLs(CClient* pClient, SCString& ssCaps) {}
void CModule::InternalServerDependentCapsOnClientCapLs(CClient* pClient, SCString& ssCaps) {
    for (const auto& [sName, pCap] : m_mServerDependentCaps) {
        if (GetNetwork() && GetNetwork()->IsServerCapAccepted(sName)) {
            if (pClient->HasCap302()) {
                CString sValue =
                    GetNetwork()->GetIRCSock()->GetCapLsValue(sName);
                if (!sValue.empty()) {
                    ssCaps.insert(sName + '=' + sValue);
                } else {
                    ssCaps.insert(sName);
                }
            } else {
                ssCaps.insert(sName);
            }
        }
    }
    OnClientCapLs(pClient, ssCaps);
}
bool CModule::IsClientCapSupported(CClient* pClient, const CString& sCap,
                                   bool bState) { return false; }
bool CModule::InternalServerDependentCapsIsClientCapSupported(
    CClient* pClient, const CString& sCap, bool bState) {
    auto it = m_mServerDependentCaps.find(sCap);
    if (it == m_mServerDependentCaps.end())
        return IsClientCapSupported(pClient, sCap, bState);
    if (!bState) return true;
    return GetNetwork() && GetNetwork()->IsServerCapAccepted(sCap);
}
void CModule::OnClientCapRequest(CClient* pClient, const CString& sCap,
                                 bool bState) {}

void CModule::InternalServerDependentCapsOnClientCapRequest(CClient* pClient,
                                                            const CString& sCap,
                                                            bool bState) {
    OnClientCapRequest(pClient, sCap, bState);
    auto it = m_mServerDependentCaps.find(sCap);
    if (it == m_mServerDependentCaps.end()) return;
    it->second->OnClientChangedSupport(pClient, bState);
}

CModule::EModRet CModule::OnClientSASLAuthenticate(
    const CString& sMechanism, const CString& sBuffer) {
    return CONTINUE;
}

CModule::EModRet CModule::OnClientSASLServerInitialChallenge(
    const CString& sMechanism, CString& sResponse) {
    return CONTINUE;
}

void CModule::OnClientGetSASLMechanisms(SCString& ssMechanisms) {}

void CModule::OnClientSASLAborted() {}

CModule::EModRet CModule::OnModuleLoading(const CString& sModName,
                                          const CString& sArgs,
                                          CModInfo::EModuleType eType,
                                          bool& bSuccess, CString& sRetMsg) {
    return CONTINUE;
}
CModule::EModRet CModule::OnModuleUnloading(CModule* pModule, bool& bSuccess,
                                            CString& sRetMsg) {
    return CONTINUE;
}
CModule::EModRet CModule::OnGetModInfo(CModInfo& ModInfo,
                                       const CString& sModule, bool& bSuccess,
                                       CString& sRetMsg) {
    return CONTINUE;
}
void CModule::OnGetAvailableMods(set<CModInfo>& ssMods,
                                 CModInfo::EModuleType eType) {}
void CModule::AddServerDependentCapability(const CString& sName,
                                           std::unique_ptr<CCapability> pCap) {
    pCap->SetModule(this);
    m_mServerDependentCaps[sName] = std::move(pCap);
}

CModules::CModules()
    : m_pUser(nullptr), m_pNetwork(nullptr), m_pClient(nullptr) {}

CModules::~CModules() { UnloadAll(); }

void CModules::UnloadAll() {
    while (size()) {
        CString sRetMsg;
        CString sModName = back()->GetModName();
        UnloadModule(sModName, sRetMsg);
    }
}

bool CModules::OnBoot() {
    for (CModule* pMod : *this) {
        try {
            if (!pMod->OnBoot()) {
                return true;
            }
        } catch (const CModule::EModException& e) {
            if (e == CModule::UNLOAD) {
                UnloadModule(pMod->GetModName());
            }
        }
    }

    return false;
}

bool CModules::OnPreRehash() {
    MODUNLOADCHK(OnPreRehash());
    return false;
}
bool CModules::OnPostRehash() {
    MODUNLOADCHK(OnPostRehash());
    return false;
}
bool CModules::OnIRCConnected() {
    MODUNLOADCHK(InternalServerDependentCapsOnIRCConnected());
    return false;
}
bool CModules::OnIRCConnecting(CIRCSock* pIRCSock) {
    MODHALTCHK(OnIRCConnecting(pIRCSock));
}
bool CModules::OnIRCConnectionError(CIRCSock* pIRCSock) {
    MODUNLOADCHK(OnIRCConnectionError(pIRCSock));
    return false;
}
bool CModules::OnIRCRegistration(CString& sPass, CString& sNick,
                                 CString& sIdent, CString& sRealName) {
    MODHALTCHK(OnIRCRegistration(sPass, sNick, sIdent, sRealName));
}
bool CModules::OnBroadcast(CString& sMessage) {
    MODHALTCHK(OnBroadcast(sMessage));
}
bool CModules::OnIRCDisconnected() {
    MODUNLOADCHK(InternalServerDependentCapsOnIRCDisconnected());
    return false;
}

bool CModules::OnChanPermission3(const CNick* pOpNick, const CNick& Nick,
                                 CChan& Channel, char cMode,
                                 bool bAdded, bool bNoChange) {
    MODUNLOADCHK(
        OnChanPermission3(pOpNick, Nick, Channel, cMode, bAdded, bNoChange));
    return false;
}
bool CModules::OnChanPermission2(const CNick* pOpNick, const CNick& Nick,
                                 CChan& Channel, unsigned char uMode,
                                 bool bAdded, bool bNoChange) {
    MODUNLOADCHK(
        OnChanPermission2(pOpNick, Nick, Channel, uMode, bAdded, bNoChange));
    return false;
}
bool CModules::OnChanPermission(const CNick& OpNick, const CNick& Nick,
                                CChan& Channel, unsigned char uMode,
                                bool bAdded, bool bNoChange) {
    MODUNLOADCHK(
        OnChanPermission(OpNick, Nick, Channel, uMode, bAdded, bNoChange));
    return false;
}
bool CModules::OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel,
                     bool bNoChange) {
    MODUNLOADCHK(OnOp2(pOpNick, Nick, Channel, bNoChange));
    return false;
}
bool CModules::OnOp(const CNick& OpNick, const CNick& Nick, CChan& Channel,
                    bool bNoChange) {
    MODUNLOADCHK(OnOp(OpNick, Nick, Channel, bNoChange));
    return false;
}
bool CModules::OnDeop2(const CNick* pOpNick, const CNick& Nick, CChan& Channel,
                       bool bNoChange) {
    MODUNLOADCHK(OnDeop2(pOpNick, Nick, Channel, bNoChange));
    return false;
}
bool CModules::OnDeop(const CNick& OpNick, const CNick& Nick, CChan& Channel,
                      bool bNoChange) {
    MODUNLOADCHK(OnDeop(OpNick, Nick, Channel, bNoChange));
    return false;
}
bool CModules::OnVoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel,
                        bool bNoChange) {
    MODUNLOADCHK(OnVoice2(pOpNick, Nick, Channel, bNoChange));
    return false;
}
bool CModules::OnVoice(const CNick& OpNick, const CNick& Nick, CChan& Channel,
                       bool bNoChange) {
    MODUNLOADCHK(OnVoice(OpNick, Nick, Channel, bNoChange));
    return false;
}
bool CModules::OnDevoice2(const CNick* pOpNick, const CNick& Nick,
                          CChan& Channel, bool bNoChange) {
    MODUNLOADCHK(OnDevoice2(pOpNick, Nick, Channel, bNoChange));
    return false;
}
bool CModules::OnDevoice(const CNick& OpNick, const CNick& Nick, CChan& Channel,
                         bool bNoChange) {
    MODUNLOADCHK(OnDevoice(OpNick, Nick, Channel, bNoChange));
    return false;
}
bool CModules::OnRawMode2(const CNick* pOpNick, CChan& Channel,
                          const CString& sModes, const CString& sArgs) {
    MODUNLOADCHK(OnRawMode2(pOpNick, Channel, sModes, sArgs));
    return false;
}
bool CModules::OnRawMode(const CNick& OpNick, CChan& Channel,
                         const CString& sModes, const CString& sArgs) {
    MODUNLOADCHK(OnRawMode(OpNick, Channel, sModes, sArgs));
    return false;
}
bool CModules::OnMode2(const CNick* pOpNick, CChan& Channel, char uMode,
                       const CString& sArg, bool bAdded, bool bNoChange) {
    MODUNLOADCHK(OnMode2(pOpNick, Channel, uMode, sArg, bAdded, bNoChange));
    return false;
}
bool CModules::OnMode(const CNick& OpNick, CChan& Channel, char uMode,
                      const CString& sArg, bool bAdded, bool bNoChange) {
    MODUNLOADCHK(OnMode(OpNick, Channel, uMode, sArg, bAdded, bNoChange));
    return false;
}
bool CModules::OnRaw(CString& sLine) { MODHALTCHK(OnRaw(sLine)); }
bool CModules::OnRawMessage(CMessage& Message) {
    MODHALTCHK(OnRawMessage(Message));
}
bool CModules::OnNumericMessage(CNumericMessage& Message) {
    MODHALTCHK(OnNumericMessage(Message));
}

bool CModules::OnClientLogin() {
    MODUNLOADCHK(OnClientLogin());
    return false;
}
bool CModules::OnClientDisconnect() {
    MODUNLOADCHK(OnClientDisconnect());
    return false;
}
bool CModules::OnUserRaw(CString& sLine) { MODHALTCHK(OnUserRaw(sLine)); }
bool CModules::OnUserRawMessage(CMessage& Message) {
    MODHALTCHK(OnUserRawMessage(Message));
}
bool CModules::OnUserCTCPReply(CString& sTarget, CString& sMessage) {
    MODHALTCHK(OnUserCTCPReply(sTarget, sMessage));
}
bool CModules::OnUserCTCPReplyMessage(CCTCPMessage& Message) {
    MODHALTCHK(OnUserCTCPReplyMessage(Message));
}
bool CModules::OnUserCTCP(CString& sTarget, CString& sMessage) {
    MODHALTCHK(OnUserCTCP(sTarget, sMessage));
}
bool CModules::OnUserCTCPMessage(CCTCPMessage& Message) {
    MODHALTCHK(OnUserCTCPMessage(Message));
}
bool CModules::OnUserAction(CString& sTarget, CString& sMessage) {
    MODHALTCHK(OnUserAction(sTarget, sMessage));
}
bool CModules::OnUserActionMessage(CActionMessage& Message) {
    MODHALTCHK(OnUserActionMessage(Message));
}
bool CModules::OnUserMsg(CString& sTarget, CString& sMessage) {
    MODHALTCHK(OnUserMsg(sTarget, sMessage));
}
bool CModules::OnUserTextMessage(CTextMessage& Message) {
    MODHALTCHK(OnUserTextMessage(Message));
}
bool CModules::OnUserNotice(CString& sTarget, CString& sMessage) {
    MODHALTCHK(OnUserNotice(sTarget, sMessage));
}
bool CModules::OnUserNoticeMessage(CNoticeMessage& Message) {
    MODHALTCHK(OnUserNoticeMessage(Message));
}
bool CModules::OnUserTagMessage(CTargetMessage& Message) {
    MODHALTCHK(OnUserTagMessage(Message));
}
bool CModules::OnPrivTagMessage(CTargetMessage& Message) {
    MODHALTCHK(OnPrivTagMessage(Message));
}
bool CModules::OnChanTagMessage(CTargetMessage& Message) {
    MODHALTCHK(OnChanTagMessage(Message));
}
bool CModules::OnInviteMessage(CInviteMessage& Message) {
    MODHALTCHK(OnInviteMessage(Message));
}
bool CModules::OnUserJoin(CString& sChannel, CString& sKey) {
    MODHALTCHK(OnUserJoin(sChannel, sKey));
}
bool CModules::OnUserJoinMessage(CJoinMessage& Message) {
    MODHALTCHK(OnUserJoinMessage(Message));
}
bool CModules::OnUserPart(CString& sChannel, CString& sMessage) {
    MODHALTCHK(OnUserPart(sChannel, sMessage));
}
bool CModules::OnUserPartMessage(CPartMessage& Message) {
    MODHALTCHK(OnUserPartMessage(Message));
}
bool CModules::OnUserTopic(CString& sChannel, CString& sTopic) {
    MODHALTCHK(OnUserTopic(sChannel, sTopic));
}
bool CModules::OnUserTopicMessage(CTopicMessage& Message) {
    MODHALTCHK(OnUserTopicMessage(Message));
}
bool CModules::OnUserTopicRequest(CString& sChannel) {
    MODHALTCHK(OnUserTopicRequest(sChannel));
}
bool CModules::OnUserQuit(CString& sMessage) {
    MODHALTCHK(OnUserQuit(sMessage));
}
bool CModules::OnUserQuitMessage(CQuitMessage& Message) {
    MODHALTCHK(OnUserQuitMessage(Message));
}

bool CModules::OnQuit(const CNick& Nick, const CString& sMessage,
                      const vector<CChan*>& vChans) {
    MODUNLOADCHK(OnQuit(Nick, sMessage, vChans));
    return false;
}
bool CModules::OnQuitMessage(CQuitMessage& Message,
                             const vector<CChan*>& vChans) {
    MODUNLOADCHK(OnQuitMessage(Message, vChans));
    return false;
}
bool CModules::OnNick(const CNick& Nick, const CString& sNewNick,
                      const vector<CChan*>& vChans) {
    MODUNLOADCHK(OnNick(Nick, sNewNick, vChans));
    return false;
}
bool CModules::OnNickMessage(CNickMessage& Message,
                             const vector<CChan*>& 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));
}
bool CModules::OnChanBufferPlayLine2(CChan& Chan, CClient& Client,
                                     CString& sLine, const timeval& tv) {
    MODHALTCHK(OnChanBufferPlayLine2(Chan, Client, sLine, tv));
}
bool CModules::OnChanBufferPlayLine(CChan& Chan, CClient& Client,
                                    CString& sLine) {
    MODHALTCHK(OnChanBufferPlayLine(Chan, Client, sLine));
}
bool CModules::OnPrivBufferStarting(CQuery& Query, CClient& Client) {
    MODHALTCHK(OnPrivBufferStarting(Query, Client));
}
bool CModules::OnPrivBufferEnding(CQuery& Query, CClient& Client) {
    MODHALTCHK(OnPrivBufferEnding(Query, Client));
}
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::OnCTCPReplyMessage(CCTCPMessage& Message) {
    MODHALTCHK(OnCTCPReplyMessage(Message));
}
bool CModules::OnPrivCTCP(CNick& Nick, CString& sMessage) {
    MODHALTCHK(OnPrivCTCP(Nick, sMessage));
}
bool CModules::OnPrivCTCPMessage(CCTCPMessage& Message) {
    MODHALTCHK(OnPrivCTCPMessage(Message));
}
bool CModules::OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) {
    MODHALTCHK(OnChanCTCP(Nick, Channel, sMessage));
}
bool CModules::OnChanCTCPMessage(CCTCPMessage& Message) {
    MODHALTCHK(OnChanCTCPMessage(Message));
}
bool CModules::OnPrivAction(CNick& Nick, CString& sMessage) {
    MODHALTCHK(OnPrivAction(Nick, sMessage));
}
bool CModules::OnPrivActionMessage(CActionMessage& Message) {
    MODHALTCHK(OnPrivActionMessage(Message));
}
bool CModules::OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) {
    MODHALTCHK(OnChanAction(Nick, Channel, sMessage));
}
bool CModules::OnChanActionMessage(CActionMessage& Message) {
    MODHALTCHK(OnChanActionMessage(Message));
}
bool CModules::OnPrivMsg(CNick& Nick, CString& sMessage) {
    MODHALTCHK(OnPrivMsg(Nick, sMessage));
}
bool CModules::OnPrivTextMessage(CTextMessage& Message) {
    MODHALTCHK(OnPrivTextMessage(Message));
}
bool CModules::OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) {
    MODHALTCHK(OnChanMsg(Nick, Channel, sMessage));
}
bool CModules::OnChanTextMessage(CTextMessage& Message) {
    MODHALTCHK(OnChanTextMessage(Message));
}
bool CModules::OnPrivNotice(CNick& Nick, CString& sMessage) {
    MODHALTCHK(OnPrivNotice(Nick, sMessage));
}
bool CModules::OnPrivNoticeMessage(CNoticeMessage& Message) {
    MODHALTCHK(OnPrivNoticeMessage(Message));
}
bool CModules::OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) {
    MODHALTCHK(OnChanNotice(Nick, Channel, sMessage));
}
bool CModules::OnChanNoticeMessage(CNoticeMessage& 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));
}
bool CModules::OnSendToClient(CString& sLine, CClient& Client) {
    MODHALTCHK(OnSendToClient(sLine, Client));
}
bool CModules::OnSendToClientMessage(CMessage& Message) {
    MODHALTCHK(OnSendToClientMessage(Message));
}
bool CModules::OnSendToIRC(CString& sLine) { MODHALTCHK(OnSendToIRC(sLine)); }
bool CModules::OnSendToIRCMessage(CMessage& Message) {
    MODHALTCHK(OnSendToIRCMessage(Message));
}
bool CModules::OnStatusCommand(CString& sCommand) {
    MODHALTCHK(OnStatusCommand(sCommand));
}
bool CModules::OnModCommand(const CString& sCommand) {
    MODUNLOADCHK(OnModCommand(sCommand));
    return false;
}
bool CModules::OnModNotice(const CString& sMessage) {
    MODUNLOADCHK(OnModNotice(sMessage));
    return false;
}
bool CModules::OnModCTCP(const CString& sMessage) {
    MODUNLOADCHK(OnModCTCP(sMessage));
    return false;
}
bool CModules::OnClientAttached() {
    MODUNLOADCHK(InternalServerDependentCapsOnClientAttached());
    return false;
}
bool CModules::OnClientDetached() {
    MODUNLOADCHK(InternalServerDependentCapsOnClientDetached());
    return false;
}

// Why MODHALTCHK works only with functions returning EModRet ? :(
bool CModules::OnServerCapAvailable(const CString& sCap, const CString& sValue) {
    bool bResult = false;
    for (CModule* pMod : *this) {
        try {
            CClient* pOldClient = pMod->GetClient();
            pMod->SetClient(m_pClient);
            if (m_pUser) {
                CUser* pOldUser = pMod->GetUser();
                CIRCNetwork* pOldNetwork = pMod->GetNetwork();
                pMod->SetUser(m_pUser);
                pMod->SetNetwork(m_pNetwork);
                bResult |=
                    pMod->InternalServerDependentCapsOnServerCap302Available(
                        sCap, sValue);
                pMod->SetUser(pOldUser);
                pMod->SetNetwork(pOldNetwork);
            } else {
                // WTF? Is that possible?
                bResult |=
                    pMod->InternalServerDependentCapsOnServerCap302Available(
                        sCap, sValue);
            }
            pMod->SetClient(pOldClient);
        } catch (const CModule::EModException& e) {
            if (CModule::UNLOAD == e) {
                UnloadModule(pMod->GetModName());
            }
        }
    }
    return bResult;
}

bool CModules::OnServerCapResult(const CString& sCap, bool bSuccess) {
    MODUNLOADCHK(InternalServerDependentCapsOnServerCapResult(sCap, bSuccess));
    return false;
}

////////////////////
// Global Modules //
////////////////////
bool CModules::OnAddUser(CUser& User, CString& sErrorRet) {
    MODHALTCHK(OnAddUser(User, sErrorRet));
}

bool CModules::OnDeleteUser(CUser& User) { MODHALTCHK(OnDeleteUser(User)); }

bool CModules::OnClientConnect(CZNCSock* pClient, const CString& sHost,
                               unsigned short uPort) {
    MODUNLOADCHK(OnClientConnect(pClient, sHost, uPort));
    return false;
}

bool CModules::OnLoginAttempt(std::shared_ptr<CAuthBase> Auth) {
    MODHALTCHK(OnLoginAttempt(Auth));
}

bool CModules::OnFailedLogin(const CString& sUsername,
                             const CString& sRemoteIP) {
    MODUNLOADCHK(OnFailedLogin(sUsername, sRemoteIP));
    return false;
}

bool CModules::OnUnknownUserRaw(CClient* pClient, CString& sLine) {
    MODHALTCHK(OnUnknownUserRaw(pClient, sLine));
}

bool CModules::OnUnknownUserRawMessage(CMessage& Message) {
    MODHALTCHK(OnUnknownUserRawMessage(Message));
}

bool CModules::OnClientCapLs(CClient* pClient, SCString& ssCaps) {
    MODUNLOADCHK(InternalServerDependentCapsOnClientCapLs(pClient, ssCaps));
    return false;
}

// Maybe create new macro for this?
bool CModules::IsClientCapSupported(CClient* pClient, const CString& sCap,
                                    bool bState) {
    bool bResult = false;
    for (CModule* pMod : *this) {
        try {
            CClient* pOldClient = pMod->GetClient();
            pMod->SetClient(m_pClient);
            if (m_pUser) {
                CUser* pOldUser = pMod->GetUser();
                CIRCNetwork* pOldNetwork = pMod->GetNetwork();
                pMod->SetUser(m_pUser);
                pMod->SetNetwork(m_pNetwork);
                bResult |=
                    pMod->InternalServerDependentCapsIsClientCapSupported(
                        pClient, sCap, bState);
                pMod->SetUser(pOldUser);
                pMod->SetNetwork(pOldNetwork);
            } else {
                // WTF? Is that possible?
                bResult |=
                    pMod->InternalServerDependentCapsIsClientCapSupported(
                        pClient, sCap, bState);
            }
            pMod->SetClient(pOldClient);
        } catch (const CModule::EModException& e) {
            if (CModule::UNLOAD == e) {
                UnloadModule(pMod->GetModName());
            }
        }
    }
    return bResult;
}

bool CModules::OnClientCapRequest(CClient* pClient, const CString& sCap,
                                  bool bState) {
    MODUNLOADCHK(
        InternalServerDependentCapsOnClientCapRequest(pClient, sCap, bState));
    return false;
}

bool CModules::OnClientSASLAuthenticate(const CString& sMechanism,
                                        const CString& sBuffer) {
    MODHALTCHK(OnClientSASLAuthenticate(sMechanism, sBuffer));
}

bool CModules::OnClientSASLServerInitialChallenge(const CString& sMechanism,
                                     CString& sResponse) {
    MODHALTCHK(OnClientSASLServerInitialChallenge(sMechanism, sResponse));
}

bool CModules::OnClientGetSASLMechanisms(SCString& ssMechanisms) {
    MODUNLOADCHK(OnClientGetSASLMechanisms(ssMechanisms));
    return false;
}

bool CModules::OnClientSASLAborted() {
    MODUNLOADCHK(OnClientSASLAborted());
    return false;
}

bool CModules::OnModuleLoading(const CString& sModName, const CString& sArgs,
                               CModInfo::EModuleType eType, bool& bSuccess,
                               CString& sRetMsg) {
    MODHALTCHK(OnModuleLoading(sModName, sArgs, eType, bSuccess, sRetMsg));
}

bool CModules::OnModuleUnloading(CModule* pModule, bool& bSuccess,
                                 CString& sRetMsg) {
    MODHALTCHK(OnModuleUnloading(pModule, bSuccess, sRetMsg));
}

bool CModules::OnGetModInfo(CModInfo& ModInfo, const CString& sModule,
                            bool& bSuccess, CString& sRetMsg) {
    MODHALTCHK(OnGetModInfo(ModInfo, sModule, bSuccess, sRetMsg));
}

bool CModules::OnGetAvailableMods(set<CModInfo>& ssMods,
                                  CModInfo::EModuleType eType) {
    MODUNLOADCHK(OnGetAvailableMods(ssMods, eType));
    return false;
}

CModule* CModules::FindModule(const CString& sModule) const {
    for (CModule* pMod : *this) {
        if (sModule.Equals(pMod->GetModName())) {
            return pMod;
        }
    }

    return nullptr;
}

bool CModules::ValidateModuleName(const CString& sModule, CString& sRetMsg) {
    for (unsigned int a = 0; a < sModule.length(); a++) {
        if (((sModule[a] < '0') || (sModule[a] > '9')) &&
            ((sModule[a] < 'a') || (sModule[a] > 'z')) &&
            ((sModule[a] < 'A') || (sModule[a] > 'Z')) && (sModule[a] != '_')) {
            sRetMsg =
                t_f("Module names can only contain letters, numbers and "
                    "underscores, [{1}] is invalid")(sModule);
            return false;
        }
    }

    return true;
}

bool CModules::LoadModule(const CString& sModule, const CString& sArgs,
                          CModInfo::EModuleType eType, CUser* pUser,
                          CIRCNetwork* pNetwork, CString& sRetMsg) {
    sRetMsg = "";

    if (!ValidateModuleName(sModule, sRetMsg)) {
        return false;
    }

    if (FindModule(sModule) != nullptr) {
        sRetMsg = t_f("Module {1} already loaded.")(sModule);
        return false;
    }

    bool bSuccess;
    bool bHandled = false;
    _GLOBALMODULECALL(OnModuleLoading(sModule, sArgs, eType, bSuccess, sRetMsg),
                      pUser, pNetwork, nullptr, &bHandled);
    if (bHandled) return bSuccess;

    CString sModPath, sDataPath;
    CModInfo Info;

    if (!FindModPath(sModule, sModPath, sDataPath)) {
        sRetMsg = t_f("Unable to find module {1}")(sModule);
        return false;
    }
    Info.SetName(sModule);
    Info.SetPath(sModPath);

    ModHandle p = OpenModule(sModule, sModPath, Info, sRetMsg);

    if (!p) return false;

    if (!Info.SupportsType(eType)) {
        dlclose(p);
        sRetMsg = t_f("Module {1} does not support module type {2}.")(
            sModule, CModInfo::ModuleTypeToString(eType));
        return false;
    }

    if (!pUser && eType == CModInfo::UserModule) {
        dlclose(p);
        sRetMsg = t_f("Module {1} requires a user.")(sModule);
        return false;
    }

    if (!pNetwork && eType == CModInfo::NetworkModule) {
        dlclose(p);
        sRetMsg = t_f("Module {1} requires a network.")(sModule);
        return false;
    }

    CModule* pModule =
        Info.GetLoader()(p, pUser, pNetwork, sModule, sDataPath, eType);
    pModule->SetDescription(Info.GetDescription());
    pModule->SetArgs(sArgs);
    pModule->SetModPath(CDir::ChangeDir(CZNC::Get().GetCurPath(), sModPath));
    push_back(pModule);

    bool bLoaded;
    try {
        bLoaded = pModule->OnLoad(sArgs, sRetMsg);
    } catch (const CModule::EModException&) {
        bLoaded = false;
        sRetMsg = t_s("Caught an exception");
    }

    if (!bLoaded) {
        UnloadModule(sModule, sModPath);
        if (!sRetMsg.empty())
            sRetMsg = t_f("Module {1} aborted: {2}")(sModule, sRetMsg);
        else
            sRetMsg = t_f("Module {1} aborted.")(sModule);
        return false;
    }

    if (!sRetMsg.empty()) {
        sRetMsg += " ";
    }
    sRetMsg += "[" + sModPath + "]";
    return true;
}

bool CModules::UnloadModule(const CString& sModule) {
    CString s;
    return UnloadModule(sModule, s);
}

bool CModules::UnloadModule(const CString& sModule, CString& sRetMsg) {
    // Make a copy incase the reference passed in is from CModule::GetModName()
    CString sMod = sModule;

    CModule* pModule = FindModule(sMod);
    sRetMsg = "";

    if (!pModule) {
        sRetMsg = t_f("Module [{1}] not loaded.")(sMod);
        return false;
    }

    bool bSuccess;
    bool bHandled = false;
    _GLOBALMODULECALL(OnModuleUnloading(pModule, bSuccess, sRetMsg),
                      pModule->GetUser(), pModule->GetNetwork(), nullptr,
                      &bHandled);
    if (bHandled) return bSuccess;

    ModHandle p = pModule->GetDLL();

    if (p) {
        delete pModule;

        for (iterator it = begin(); it != end(); ++it) {
            if (*it == pModule) {
                erase(it);
                break;
            }
        }

        dlclose(p);
        sRetMsg = t_f("Module {1} unloaded.")(sMod);

        return true;
    }

    sRetMsg = t_f("Unable to unload module {1}.")(sMod);
    return false;
}

bool CModules::ReloadModule(const CString& sModule, const CString& sArgs,
                            CUser* pUser, CIRCNetwork* pNetwork,
                            CString& sRetMsg) {
    // Make a copy incase the reference passed in is from CModule::GetModName()
    CString sMod = sModule;

    CModule* pModule = FindModule(sMod);

    if (!pModule) {
        sRetMsg = t_f("Module [{1}] not loaded.")(sMod);
        return false;
    }

    CModInfo::EModuleType eType = pModule->GetType();
    pModule = nullptr;

    sRetMsg = "";
    if (!UnloadModule(sMod, sRetMsg)) {
        return false;
    }

    if (!LoadModule(sMod, sArgs, eType, pUser, pNetwork, sRetMsg)) {
        return false;
    }

    sRetMsg = t_f("Reloaded module {1}.")(sMod);
    return true;
}

bool CModules::GetModInfo(CModInfo& ModInfo, const CString& sModule,
                          CString& sRetMsg) {
    if (!ValidateModuleName(sModule, sRetMsg)) {
        return false;
    }

    CString sModPath, sTmp;

    bool bSuccess;
    bool bHandled = false;
    GLOBALMODULECALL(OnGetModInfo(ModInfo, sModule, bSuccess, sRetMsg),
                     &bHandled);
    if (bHandled) return bSuccess;

    if (!FindModPath(sModule, sModPath, sTmp)) {
        sRetMsg = t_f("Unable to find module {1}.")(sModule);
        return false;
    }

    return GetModPathInfo(ModInfo, sModule, sModPath, sRetMsg);
}

bool CModules::GetModPathInfo(CModInfo& ModInfo, const CString& sModule,
                              const CString& sModPath, CString& sRetMsg) {
    if (!ValidateModuleName(sModule, sRetMsg)) {
        return false;
    }

    ModInfo.SetName(sModule);
    ModInfo.SetPath(sModPath);

    ModHandle p = OpenModule(sModule, sModPath, ModInfo, sRetMsg);
    if (!p) return false;

    dlclose(p);

    return true;
}

void CModules::GetAvailableMods(set<CModInfo>& ssMods,
                                CModInfo::EModuleType eType) {
    ssMods.clear();

    unsigned int a = 0;
    CDir Dir;

    ModDirList dirs = GetModDirs();

    while (!dirs.empty()) {
        Dir.FillByWildcard(dirs.front().first, "*.so");
        dirs.pop();

        for (a = 0; a < Dir.size(); a++) {
            CFile& File = *Dir[a];
            CString sName = File.GetShortName();
            CString sPath = File.GetLongName();
            CModInfo ModInfo;
            sName.RightChomp(3);

            CString sIgnoreRetMsg;
            if (GetModPathInfo(ModInfo, sName, sPath, sIgnoreRetMsg)) {
                if (ModInfo.SupportsType(eType)) {
                    ssMods.insert(ModInfo);
                }
            }
        }
    }

    GLOBALMODULECALL(OnGetAvailableMods(ssMods, eType), NOTHING);
}

void CModules::GetDefaultMods(set<CModInfo>& ssMods,
                              CModInfo::EModuleType eType) {
    GetAvailableMods(ssMods, eType);

    const map<CString, CModInfo::EModuleType> ns = {
        {"chansaver", CModInfo::UserModule},
        {"controlpanel", CModInfo::UserModule},
        {"corecaps", CModInfo::GlobalModule},
        {"saslplainauth", CModInfo::GlobalModule},
        {"simple_away", CModInfo::NetworkModule},
        {"webadmin", CModInfo::GlobalModule}};

    auto it = ssMods.begin();
    while (it != ssMods.end()) {
        auto it2 = ns.find(it->GetName());
        if (it2 != ns.end() && it2->second == eType) {
            ++it;
        } else {
            it = ssMods.erase(it);
        }
    }
}

bool CModules::FindModPath(const CString& sModule, CString& sModPath,
                           CString& sDataPath) {
    CString sMod = sModule;
    CString sDir = sMod;
    if (!sModule.Contains(".")) sMod += ".so";

    ModDirList dirs = GetModDirs();

    while (!dirs.empty()) {
        sModPath = dirs.front().first + sMod;
        sDataPath = dirs.front().second;
        dirs.pop();

        if (CFile::Exists(sModPath)) {
            sDataPath += sDir;
            return true;
        }
    }

    return false;
}

CModules::ModDirList CModules::GetModDirs() {
    ModDirList ret;
    CString sDir;

#ifdef RUN_FROM_SOURCE
    // ./modules
    sDir = CZNC::Get().GetCurPath() + "/modules/";
    ret.push(std::make_pair(sDir, sDir + "data/"));
#endif

    // ~/.znc/modules
    sDir = CZNC::Get().GetModPath() + "/";
    ret.push(std::make_pair(sDir, sDir));

    // <moduledir> and <datadir> (<prefix>/lib/znc)
    ret.push(std::make_pair(_MODDIR_ + CString("/"),
                            _DATADIR_ + CString("/modules/")));

    return ret;
}

ModHandle CModules::OpenModule(const CString& sModule, const CString& sModPath,
                               CModInfo& Info, CString& sRetMsg) {
    // Some sane defaults in case anything errors out below
    sRetMsg.clear();

    if (!ValidateModuleName(sModule, sRetMsg)) {
        return nullptr;
    }

    // The second argument to dlopen() has a long history. It seems clear
    // that (despite what the man page says) we must include either of
    // RTLD_NOW and RTLD_LAZY and either of RTLD_GLOBAL and RTLD_LOCAL.
    //
    // RTLD_NOW vs. RTLD_LAZY: We use RTLD_NOW to avoid ZNC dying due to
    // failed symbol lookups later on. Doesn't really seem to have much of a
    // performance impact.
    //
    // RTLD_GLOBAL vs. RTLD_LOCAL: If perl is loaded with RTLD_LOCAL and later
    // on loads own modules (which it apparently does with RTLD_LAZY), we will
    // die in a name lookup since one of perl's symbols isn't found. That's
    // worse than any theoretical issue with RTLD_GLOBAL.
    ModHandle p = dlopen((sModPath).c_str(), RTLD_NOW | RTLD_GLOBAL);

    if (!p) {
        // dlerror() returns pointer to static buffer, which may be overwritten
        // very soon with another dl call also it may just return null.
        const char* cDlError = dlerror();
        CString sDlError = cDlError ? cDlError : t_s("Unknown error");
        sRetMsg = t_f("Unable to open module {1}: {2}")(sModule, sDlError);
        return nullptr;
    }

    const CModuleEntry* (*fpZNCModuleEntry)() = nullptr;
    // man dlsym(3) explains this
    *reinterpret_cast<void**>(&fpZNCModuleEntry) = dlsym(p, "ZNCModuleEntry");
    if (!fpZNCModuleEntry) {
        dlclose(p);
        sRetMsg = t_f("Could not find ZNCModuleEntry in module {1}")(sModule);
        return nullptr;
    }
    const CModuleEntry* pModuleEntry = fpZNCModuleEntry();

    if (std::strcmp(pModuleEntry->pcVersion, VERSION_STR) ||
        std::strcmp(pModuleEntry->pcVersionExtra, VERSION_EXTRA)) {
        sRetMsg = t_f(
            "Version mismatch for module {1}: core is {2}, module is built for "
            "{3}. Recompile this module.")(
            sModule, VERSION_STR VERSION_EXTRA,
            CString(pModuleEntry->pcVersion) + pModuleEntry->pcVersionExtra);
        dlclose(p);
        return nullptr;
    }

    if (std::strcmp(pModuleEntry->pcCompileOptions,
                    ZNC_COMPILE_OPTIONS_STRING)) {
        sRetMsg = t_f(
            "Module {1} is built incompatibly: core is '{2}', module is '{3}'. "
            "Recompile this module.")(sModule, ZNC_COMPILE_OPTIONS_STRING,
                                      pModuleEntry->pcCompileOptions);
        dlclose(p);
        return nullptr;
    }

    CTranslationDomainRefHolder translation("znc-" + sModule);
    pModuleEntry->fpFillModInfo(Info);

    sRetMsg = "";
    return p;
}

CModCommand::CModCommand()
    : m_sCmd(), m_pFunc(nullptr), m_Args(""), m_Desc("") {}

CModCommand::CModCommand(const CString& sCmd, CModule* pMod, ModCmdFunc func,
                         const CString& sArgs, const CString& sDesc)
    : m_sCmd(sCmd),
      m_pFunc([pMod, func](const CString& sLine) { (pMod->*func)(sLine); }),
      m_Args(sArgs),
      m_Desc(sDesc) {}

CModCommand::CModCommand(const CString& sCmd, CmdFunc func,
                         const COptionalTranslation& Args,
                         const COptionalTranslation& Desc)
    : m_sCmd(sCmd), m_pFunc(std::move(func)), m_Args(Args), m_Desc(Desc) {}

void CModCommand::InitHelp(CTable& Table) {
    Table.SetStyle(CTable::ListStyle);
    Table.AddColumn(t_s("Command", "modhelpcmd"));
    Table.AddColumn(t_s("Description", "modhelpcmd"));
}

void CModCommand::AddHelp(CTable& Table) const {
    Table.AddRow();
    Table.SetCell(t_s("Command", "modhelpcmd"),
                  GetCommand() + (GetArgs().empty() ? "" : " ") + GetArgs());
    Table.SetCell(t_s("Description", "modhelpcmd"), GetDescription());
}

CString CModule::t_s(const CString& sEnglish, const CString& sContext) const {
    return CTranslation::Get().Singular("znc-" + GetModName(), sContext,
                                        sEnglish);
}

CInlineFormatMessage CModule::t_f(const CString& sEnglish,
                                  const CString& sContext) const {
    return CInlineFormatMessage(t_s(sEnglish, sContext));
}

CInlineFormatMessage CModule::t_p(const CString& sEnglish,
                                  const CString& sEnglishes, int iNum,
                                  const CString& sContext) const {
    return CInlineFormatMessage(CTranslation::Get().Plural(
        "znc-" + GetModName(), sContext, sEnglish, sEnglishes, iNum));
}

CDelayedTranslation CModule::t_d(const CString& sEnglish,
                                 const CString& sContext) const {
    return CDelayedTranslation("znc-" + GetModName(), sContext, sEnglish);
}

CString CModInfo::t_s(const CString& sEnglish, const CString& sContext) const {
    return CTranslation::Get().Singular("znc-" + GetName(), sContext, sEnglish);
}
