To create new wiki account, please join us on #znc at Libera.Chat and ask admins to create a wiki account for you. You can say thanks to spambots for this inconvenience.


From ZNC
Revision as of 16:39, 17 December 2011 by DarthGandalf (talk | contribs) (Created page with "{{DISPLAYTITLE:modpython}} Modpython allows you to use modules written on python 3. '''This document is in draft, some things can be changed''' == Compiling == First, you n...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Modpython allows you to use modules written on python 3.

This document is in draft, some things can be changed


First, you need to use ./configure with option --enable-python.

To build modpython, you need some files produced by SWIG. They are not shipped with ZNC because of huge size (several megabytes). There're 2 ways to get them:

  1. Install swig. Needed files will be generated automatically when you run make in znc source dir to compile znc.
  2. Download and unpack these files to <znc-source>/modules/modpython/ (_znc_core.cpp and modpython.i should be in the same dir), and add option --disable-swig to ./configure


Loading and unloading of python3 modules is similar to C++ modules. For example, you can use /znc loadmod or webadmin.

If you unload modpython, all python modules are automatically unloaded too.


This global module takes no arguments.

Read loading modules to learn more about loading modules.

Writing new python3 modules


Every python module is file named like (.pyc and .so are supported too, but I doubt that you want to write ZNC module as C python extension ;) ) and is located in usual modules directories. The file must contain class with exactly the same name as the module itself. The class should be derived from znc.Module.


import znc

class pythonexample(znc.Module):
    description = "Example python3 module for ZNC"

    def OnChanMsg(self, nick, channel, message):
        self.PutModule("Hey, {0} said {1} on {2}".format(nick.GetNick(), message.s, channel.GetName()))
        return znc.CONTINUE

All callbacks have the same name as in C++, and have the same arguments, but with reference to self before first argument, as usually in python.

sub OnShutdown is used as destructor (instead of python's __del__). OnShutdown is called when the module is going to be unloaded.

If a callback returns None, a reasonable default is substituted. If a callback raises an exception, the default value is assumed too.

When a module callback should return CModule::EModRet, you can use values as znc.CModule.CONTINUE or just znc.CONTINUE.

ZNC C++ API can be found here. Most of it should just work for python modules. The following text describes mostly features, differences and caveats.


All ZNC classes are accessible from python with znc. prefix. The exception is CString. All uses of CString by value is transparently translated to/from python string objects.

// C++
void Foo(const CString& s);
# python

The same for case where you get CString by value:

// C++
class CModule {
    virtual bool OnLoad(const CString& sArgsi, CString& sMessage);
# python
class foo(znc.Module):
    def OnLoad(self, args, message):
        if args == "bar":
            return True
        return False

If you need to use CString by reference, use class znc.String and its attribute s:

// C++
void Foo(CString& s) {
    s = "bar";
# python
x = znc.String()
print(x.s); # prints 'bar' to stdout

The same if you get CString& as argument:

// C++
class CModule {
    virtual bool OnLoad(const CString& sArgsi, CString& sMessage);
# python
class foo(znc.Module):
    def OnLoad(self, args, message):
        message.s = 'bar'
        return True

Module's NV

module.nv is a dict-like object, which can be used as normal dict, but stores it's data on disk. Both keys and values should be strings.

class foo(znc.Module):
    def OnLoad(self, args, message):
        self.nv['bar'] = 'baz'
        if 'abcde' in self.nv:
                message.s = self.nv['qwerty']
            except KeyError:
                message.s = self.nv['abcde']
        for k, v = self.nv.items():


To show your module's page or subpages in the menu, need to define GetWebMenuTitle which should return visual name for the module.

class test(znc.Module):
    def GetWebMenuTitle(self):
        return "Python test module"


Probably need to explain here that it's not CGI and where to write HTML?

Instead of operator[] use set:

// C++
CTemplate& tmpl = ...;
tmpl["name"] = "value";
CTemplate& row = tmpl.AddRow("SomeTable");
row["foo"] = "bar";
# python equivalent
tmpl = ...;
tmpl.set("name", "value")
row = tmpl.AddRow("SomeTable")
row.set("foo", "bar")


If you want to have subpages for the module, use helper function znc.CreateWebSubPage. It accepts one required argument - name of the subpage, and several optional arguments:

  • title - text for displaying subpage name. By default it's the same as name.
  • params - dict of parameters which will be used in URL linking to the subpage.
  • admin - set to True if subpage should be accessible only by admins.

There're 2 ways: using AddSubPage/ClearSubPages and overriding GetSubPages.

def OnLoad(self, args, message):
    self.AddSubPage(znc.CreateWebSubPage('page2', title='Page N2'));
    self.AddSubPage(znc.CreateWebSubPage('page3', params=dict(var1='value1', var2='value2'), admin=True);
    return True

The second way - to override GetSubPages. Perhaps(?) it may be better if list of subpages changes often in runtime.

But don't return pointer to local variable! Python destroys objects when reference count goes to zero. In these 2 examples additional reference to returned object is stored in self.webpages

def OnLoad(self, args, message):
    self.webpages = znc.VWebSubPages()
    return True
def OnModCommand(self, cmd):
def GetSubPages(self):
    return self.webpages
def GetSubPages(self):
    result = znc.VWebSubPages()
    self.webpages = result
    return result


Use helper function CreateTimer. It gets following arguments:

  • timer (required) - reference to your Timer class. It should be derived from znc.Timer. You can override 2 methods: RunJob and OnShutdown.
  • interval - Interval between calls, in seconds. Default is 10.
  • cycles - Number of times to run the sub. 0 means infinite. Default is 1.
  • description - Text description of the timer. Default doesn't matter.
import znc

class testtimer(znc.Timer):
    def RunJob(self):
        self.GetModule.PutStatus('foo {0}'.format(self.msg))

class timertest(znc.Module):
    def OnModCommand(self, cmd):
        timer = self.CreateTimer(testtimer, interval=4, cycles=1, description='Says "foo bar" after 4 seconds')
        timer.msg = 'bar'

You can use methods of C++ class CTimer (like Stop) for your timer.


If module needs to know whether ZNC was compiled with IPv6, SSL and c-ares support, you can use special variables, which are True if the feature is supported.

if znc.HaveIPv6:
if znc.HaveSSL:
if znc.HaveCAres:

All sockets are instances of special classes derived from znc.Socket. znc.Socket have all the same methods as Csock, except Connect, Listen and Write. Csock's reference can be found here. To get reference to associated module, use GetModule. Callbacks have different names from ones of Csock, they are described later.

To create socket, use module's method CreateSocket. First argument is reference to your socket class. The function creates socket and calls method Init of it with the rest of arguments. Reference to the new socket is returned.

To connect socket, use method Connect. It gets 2 required arguments - hostname and port, and several optional arguments:

  • timeout - Time in seconds to wait for connection. Default is 60.
  • ssl - Whether to use SSL for connection.
  • bindhost - Local interface to use for the connection.

Returns true value if connection scheduled successfully.

import znc
class connsock(znc.Socket):
    def Init(self, line): # line and other arguments, including named arguments can be specified in CreateSocket
        self.Connect('', 80)
    def OnReadLine(self, line):
        self.GetModule().PutStatus(line) # this puts also \n and \r to status, which is not very good, but this is just an example, so...

class networkconn(znc.Module):
    def OnModCommand(self, cmd):
        sock = self.CreateSocket(connsock, "GET {0} HTTP/1.0\r\n".format(cmd))
import znc
class conn(znc.Socket):
    def OnReadLine(self, line):

class socketconn(znc.Module):
    def OnModCommand(self, cmd):
        sock = self.CreateSocket(conn)
        sock.Connect('', 443, ssl=True)
        sock.Write("GET {0} HTTP/1.0\r\n\r\n".format(cmd))

To create listening socket, use method Listen. It gets following optional named arguments:

  • port - Port number to listen on. If not presented, random port is choosed.
  • bindhost - Interface to listen on. If not presented, socket will listen on all interfaces.
  • addrtype - Chooses protocol family. Possible values are 'all', 'ipv4' and 'ipv6'. Default is all.
  • ssl - Whether to use SSL for incoming connections.
  • maxconns - Maximum number of connections. Default is SOMAXCONN.
  • timeout - time in seconds, for timeout.

Returns 0 on error and port number on success.

import znc

class accepted(znc.Socket):
    def Init(self, host, port):
        self.Write("Hello, {0}:{1}!\n".format(host, port))
    def OnReadData(self, data):
        self.WriteBytes(data) # echo back everything

class listensock(znc.Socket):
    def OnAccepted(self, host, port):
        return self.GetModule().CreateSocket(accepted, host, port)

class listmodule(znc.Module):
    def OnLoad(self, args, message):
        sock = self.CreateSocket(listensock)
        port = sock.Listen(ssl=True, addrtype='ipv6');
        if port > 0:
            message.s = "Listening on all IPv6 interfaces on port {0} using SSL".format(port)

Use Write to write strings, and WriteBytes to write binary data.

Sockets can override following callbacks:

  • Init - is called from CreateSocket, first argument is reference to socket, the rest is from arguments to CreateSocket.
  • OnConnected
  • OnDisconnected
  • OnTimeout
  • OnConnectionRefused
  • OnReadData - gets bytes as second argument
  • OnReadLine - is called for every new line from socket. The line, including ending \n (or \r\n) is in argument. It's called only if you enabled this feature for the socket.
  • OnAccepted - is called for listening socket for every new connection. Arguments are hostname and port of remote end. The callback should return None if you don't need the connection, or reference to new socket, which will be used for this connection.
  • OnShutdown - destructor of the socket.

If callback On* raises an exception, the socket is closed, but if you want to close socket, use method Close instead. If Init raises an exception, behavior is undefined.

Getting ZNC version

If you just want to show ZNC version to humans, usually just znc.CZNC.GetTag() is good.

znc.CZNC.GetTag() # Returns, for example, 'ZNC 0.097 -'

Check ZNC C++ documentation for details.

For getting ZNC version, you can use read following variables:

znc.Version # For example, number 0.097
znc.VersionMajor # 0
znc.VersionMinor # 97
znc.VersionExtra # build-specific string