diff --git a/modules/modpython.cpp b/modules/modpython.cpp index a8f4bf3d..26a616a2 100644 --- a/modules/modpython.cpp +++ b/modules/modpython.cpp @@ -505,6 +505,25 @@ CPySocket::~CPySocket() { Py_CLEAR(m_pyObj); } +CPyModule* CPyModCommand::GetModule() { + return this->m_pModule; +} + +void CPyModCommand::operator()(const CString& sLine) { + PyObject* pyRes = PyObject_CallMethod( + m_pyObj, const_cast("__call__"), const_cast("s"), + sLine.c_str()); + if (!pyRes) { + CString sRetMsg = m_pModPython->GetPyExceptionStr(); + DEBUG("oops, something went wrong when calling command: " << sRetMsg); + } + Py_CLEAR(pyRes); +} + +CPyModCommand::~CPyModCommand() { + Py_CLEAR(m_pyObj); +} + template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("modpython"); diff --git a/modules/modpython/module.h b/modules/modpython/module.h index 9db69c84..7ea54db7 100644 --- a/modules/modpython/module.h +++ b/modules/modpython/module.h @@ -334,3 +334,35 @@ class CModulesIter { CModules* m_pModules; CModules::const_iterator m_it; }; + +class ZNC_EXPORT_LIB_EXPORT CPyModCommand : public CModCommand { + CPyModule* m_pModule; + CModPython* m_pModPython; + PyObject* m_pyObj; + + void operator()(const CString& sLine); + + public: + CPyModCommand(CPyModule* pModule, + const CString& sCmd, const COptionalTranslation& sArgs, + const COptionalTranslation& sDesc, PyObject *pyObj) + : CModCommand(sCmd, [=](const CString& sLine) { (*this)(sLine); }, sArgs, + sDesc), + m_pModule(pModule), + m_pModPython(pModule->GetModPython()), + m_pyObj(pyObj) { + Py_INCREF(pyObj); + pModule->AddCommand(*this); + } + virtual ~CPyModCommand(); + + CPyModule* GetModule(); +}; + +inline CPyModCommand* CreatePyModCommand(CPyModule* pModule, + const CString& sCmd, + const COptionalTranslation& sArgs, + const COptionalTranslation& sDesc, + PyObject* pyObj) { + return new CPyModCommand(pModule, sCmd, sArgs, sDesc, pyObj); +} diff --git a/modules/modpython/znc.py b/modules/modpython/znc.py index 371687e0..26b98590 100644 --- a/modules/modpython/znc.py +++ b/modules/modpython/znc.py @@ -197,7 +197,9 @@ class Module: num) return fmt.format - # TODO is "t_d" needed for python? Maybe after AddCommand is implemented + @classmethod + def t_d(cls, english, context=''): + return CDelayedTranslation('znc-' + cls.__name__, context, english) def OnLoad(self, sArgs, sMessage): return True @@ -295,7 +297,7 @@ class Module: pass def OnModCommand(self, sCommand): - pass + self.HandleCommand(sCommand) def OnModNotice(self, sMessage): pass @@ -429,6 +431,16 @@ class Module: def OnSendToIRC(self, sLine): pass + # Command stuff + def AddCommand(self, cls, *args, **kwargs): + cmd = cls(*args, **kwargs) + cmd._cmodcommand = CreatePyModCommand(self._cmod, cls.cmd, + COptionalTranslation(cls.args), + COptionalTranslation(cls.desc), + cmd) + + return cmd + # Global modules def OnAddUser(self, User, sErrorRet): pass @@ -672,6 +684,18 @@ class Module: pass +class Command: + cmd = '' + args = '' + desc = '' + + def __call__(self, sLine): + pass + + def GetModule(self): + return self._cmodcommand.GetModule().GetNewPyObj() + + def make_inherit(cl, parent, attr): def make_caller(parent, name, attr): return lambda self, *a: parent.__dict__[name](self.__dict__[attr], *a) @@ -688,6 +712,7 @@ def make_inherit(cl, parent, attr): make_inherit(Socket, CPySocket, '_csock') make_inherit(Module, CPyModule, '_cmod') make_inherit(Timer, CPyTimer, '_ctimer') +make_inherit(Command, CPyModCommand, '_cmodcommand') class ZNCModuleLoader(importlib.abc.SourceLoader): diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp index 7a5718ff..3fcd707f 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -296,5 +296,37 @@ TEST_F(ZNCTest, ModpythonModperl) { client.ReadUntil("Loaded module modperl"); } +TEST_F(ZNCTest, ModpythonCommand) { + if (QProcessEnvironment::systemEnvironment().value( + "DISABLED_ZNC_PERL_PYTHON_TEST") == "1") { + return; + } + + auto znc = Run(); + znc->CanLeak(); + + InstallModule("cmdtest.py", R"( + import znc + + class cmdtest(znc.Module): + def OnLoad(self, args, message): + self.AddCommand(testcmd) + return True + + class testcmd(znc.Command): + cmd = 'ping' + + def __call__(self, line): + self.GetModule().PutModule('pong') + )"); + + auto ircd = ConnectIRCd(); + auto client = LoginClient(); + client.Write("znc loadmod modpython"); + client.Write("znc loadmod cmdtest"); + client.Write("PRIVMSG *cmdtest :ping"); + client.ReadUntil("pong"); +} + } // namespace } // namespace znc_inttest