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.
Modperl: Difference between revisions
DarthGandalf (talk | contribs) |
DarthGandalf (talk | contribs) |
||
Line 107: | Line 107: | ||
Several settings can be configured, using the functions like shown below: | Several settings can be configured, using the functions like shown below: | ||
sub module_types { ... (see above section) } | sub module_types { ... (see above section) } | ||
sub description { "This module does this and that" } | sub description { "This module does this and that" } | ||
sub wiki_page { "my_module" } | sub wiki_page { "my_module" } | ||
sub has_args { 1 } | sub has_args { 1 } # the default is 0 | ||
sub args_help_text { "The arguments are foo and bar" } | sub args_help_text { "The arguments are foo and bar" } | ||
Revision as of 20:08, 21 January 2020
This module is a part of ZNC. This module is shipped with ZNC by default. If you have the right "LoadMod" you can activate it with /znc LoadMod modperl The code for this module can be found here. |
Modperl allows you to use modules written on perl.
This page describes module since ZNC 0.095. If you still need help on old modperl, look here.
Compiling
First, you need to use ./configure with option --enable-perl.
If you're building from git, you need to have SWIG installed. If you're building from tarball (nightly or release), SWIG is not required.
Usage
Loading and unloading of perl modules is similar to C++ modules. For example, you can use /znc loadmod
or webadmin.
If you unload modperl, all perl modules are automatically unloaded too.
Arguments
This global module takes no arguments.
Read loading modules to learn more about loading modules.
Writing new perl modules
Basics
Every perl module is file named like modulename.pm
and is located in usual modules directories.
The file must contain package with exactly the same name as the module itself.
The module should be derived from ZNC::Module
.
# perlexample.pm package perlexample; use base 'ZNC::Module'; sub description { "Example perl module for ZNC" } sub OnChanMsg { my $self = shift; my ($nick, $chan, $msg) = @_; $self->PutModule("Hey, ".$nick->GetNick." said [$msg] on ".$chan->GetName); return $ZNC::CONTINUE; } 1;
If you want to define several perl packages inside your module, you should name them as subpackages of your module package. See Sockets section for example.
Description of the module is the return value from a sub description
.
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 perl.
The exception is callbacks which get vector<...> as last argument:
virtual void OnQuit(const CNick& Nick, const CString& sMessage, ''const vector<CChan*>& vChans'');
sub OnQuit { my ($self, $nick, $message, ''@chans'') = @_; for (@chans) { $self->PutIRC("PRIVMSG ".$_->GetName." :Poor ".$nick->GetNick." :("); } }
sub OnShutdown
is used as destructor (instead of perl's DESTROY
).
OnShutdown
is called when the module is going to be unloaded.
If a callback returns undef, a reasonable default is substituted.
Remember that if execution comes to end of sub, last evaluated value is returned!
If a callback dies/croaks, the default value is assumed too, the behavior of what happens to arguments is undefined.
When a module callback should return CModule::EModRet
, you can use values as $ZNC::CModule::CONTINUE
or just $ZNC::CONTINUE
.
Don't begin names of your member data fields with underscore (_) - some of them are used by modperl internally.
sub OnShutdown { my $self = shift; $self->{foo} = "foo"; # OK $self->{_bar} = "bar"; # Fail, probably will work but can randomly stop working. }
ZNC C++ API can be found here. Most of it should just work for perl modules. The following text describes mostly features, differences and caveats.
Module types
Before ZNC 0.207 only user perl modules are supported. Since ZNC 0.207 network, user and global modules are supported. By default every module is network-only.
If you want to make your module accessible only at user level, use this:
package perlusermod; use base 'ZNC::Module'; sub module_types { $ZNC::CModInfo::UserModule }
For global modules use this:
package perlglobalmod; use base 'ZNC::Module'; sub module_types { $ZNC::CModInfo::GlobalModule }
If you want to make your module to be loadable as both user module and network module, use this:
package perlusernetworkmod; use base 'ZNC::Module'; sub module_types { $ZNC::CModInfo::UserModule, $ZNC::CModInfo::NetworkModule }
The first element of the list is the default module type. It's used when no type is specified. So /znc loadmod perlusernetworkmod
will load it as user module.
Module metadata
Several settings can be configured, using the functions like shown below:
sub module_types { ... (see above section) } sub description { "This module does this and that" } sub wiki_page { "my_module" } sub has_args { 1 } # the default is 0 sub args_help_text { "The arguments are foo and bar" }
Strings
All ZNC classes are accessible from perl with ZNC::
prefix.
The exception is CString
. All uses of CString
by value are transparently translated to/from perl string scalars.
// C++ void Foo(const CString& s);
# perl my $str = "bar"; ZNC::Foo($str); # OK my $num = 42; ZNC::Foo($num); # OK (only for ZNC >= 0.097) ZNC::Foo("$num"); # OK ZNC::Foo($num . ""); # OK ZNC::Foo(ZNC::String->new($num)); # OK, see below about ZNC::String
If you need to call a function which gets CString
by reference, and returns a value in it, there's a class named ZNC::String
:
// C++ void Foo(CString& s) { s = "bar"; }
# perl my $s = ZNC::String->new; ZNC::Foo($s); print $s->GetPerlStr; # prints 'bar' to stdout print $s; # The same, only for ZNC >= 1.7.0.
As you see, to get normal string from ZNC::String
there's a method GetPerlStr
. Since ZNC 1.7.0 it's called automatically when string is needed.
You can construct non-empty ZNC::String
using an argument to new
.
This constructor can get string scalars, integer scalars, float scalars.
When you implement a module hook which accepts CString&
, no ZNC::String
magic is needed, it just works:
sub OnChanMsg { my $self = shift; # @_ == (nick, channel, message) if ($_[0]->GetNick eq "Fish" || $_[2] eq "kwaa") { $_[2] = "moo!" } # the code above can be rewritten as follows: my ($nick, $channel, $message) = map {\$_} @_; if ($$nick->GetNick eq "Fish" || $$message eq "kwaa") { $$message = "moo!"; } # alternatively, use \shift }
Message types
Since ZNC 1.7.0.
To convert between various subclasses of CMessage
, use this:
my $num_msg = $msg->As('CNumericMessage');
Module's NV
There're some issues with using std iterators from perl, so class CModule
got new methods: GetNVKeys
which returns list of names of all NV values of the module, and ExistsNV
which checks if specified variable exists.
sub OnModCommand { my $self = shift; my @nvkeys = $self->GetNVKeys; if ($self->ExistsNV("foo")) { $self->SetNV("bar", $self->GetNV("foo")); $self->DelNV("foo"); } .... }
Also there's another interface for accessing NVs - perl hash. (But for big number of values it's slow)
sub OnModCommand { my $self = shift; my $nv = $self->NV; while (my ($key, $value) = each %$nv) { ... } my @nvkeys = keys %$nv; $nv->{foo} = "bar"; $self->PutModule($nv->{foo}); delete $nv->{foo}; }
Of course, you can use $self->{foo} for storing temporary values, but NV data is stored on disk.
Web
See WebMods for an overview.
To show your module's page or subpages in the menu, need to define GetWebMenuTitle
which should return visual name for the module.
sub GetWebMenuTitle { "Perl test module" }
CTemplate
Instead of operator[] use set:
// C++ CTemplate& tmpl = ...; tmpl["name"] = "value"; CTemplate& row = tmpl.AddRow("SomeTable"); row["foo"] = "bar";
# perl equivalent my $tmpl = ...; $tmpl->set("name", "value"); my $row = $tmpl->AddRow("SomeTable"); $row->set("foo", "bar");
Subpages
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 named arguments:
- title - text for displaying subpage name. By default it's the same as name.
- params - reference to hash of parameters which will be used in URL linking to the subpage.
- admin - set to true value if subpage should be accessible only by admins.
There're 2 ways: using AddSubPage
/ClearSubPages
and overriding GetSubPages
.
sub OnLoad { my $self = shift; $self->AddSubPage(ZNC::CreateWebSubPage('page1')); $self->AddSubPage(ZNC::CreateWebSubPage('page2', title=>'Page N2')); $self->AddSubPage(ZNC::CreateWebSubPage('page3', params=>{var1=>'value1', var2=>'value2'}, admin=>1); 1 }
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! Perl destroys objects when reference count goes to zero. In these 2 examples additional reference to returned object is stored in $self->{webpages}
.
sub OnLoad { my $self = shift; $self->{webpages} = ZNC::VWebSubPages->new; 1 } sub OnModCommand { my $self = shift; my $cmd = shift; $self->{webpages}->push(ZNC::CreateWebSubPage($cmd)); } sub GetSubPages { my $self = shift; return $self->{webpages} }
sub GetSubPages { my $self = shift; my $result = ZNC::VWebSubPages->new; $result->push(ZNC::CreateWebSubPage('foo')); $self->{webpages} = $result; return $result; }
Timers
There're 2 APIs: first uses references to subs, second uses OOP packages.
Ref API
Use helper function CreateTimer
.
It gets following named arguments:
- task (required) - Reference to a sub which will be called. The sub gets reference to your module as first argument, and context as named argument.
- 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.
- context - Arbitrary value, which task will get as named argument context.
sub foo { my $self = shift; my %arg = @_; $self->PutStatus("foo ".$arg{context}); } sub OnModCommand { my $self = shift; $self->CreateTimer(task=>\&foo, interval=>5, context=>'bar', description=>"Timer which puts 'foo bar' to status in 5 secs after user wrote something to the module"); $self->CreateTimer(task=>sub {}, cycles=>0, interval=>1, description=>"Timer which every 1 second does nothing"); }
OOP API
(available since ZNC 0.097)
Use helper function CreateTimer
. It gets following named arguments:
- task (required) - name of your Timer class. It should be derived from
ZNC::Timer
. You can override 2 methods:RunJob
andOnShutdown
. - 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.
# timerooptest.pm use strict; use warnings; package timerooptest::timer; use base 'ZNC::Timer'; sub RunJob { my $self = shift; $self->GetModule->PutStatus('foo '.$self->{msg}); } package timerooptest; use base 'ZNC::Module'; sub OnModCommand { my $self = shift; my $timer = $self->CreateTimer(task=>'timerooptest::timer', 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.
Sockets
If module needs to know whether ZNC was compiled with IPv6, SSL and c-ares support, you can use special functions, which return true scalar 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
has all the same methods as Csock
, except Connect
and Listen
.
Csock's reference can be found here.
To get reference to the 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 the name of 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 named 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.
# testmodule.pm package testmodule::connsock; use base 'ZNC::Socket'; sub Init { my $self = shift; my $line = shift; # this and following arguments can be specified in CreateSocket $self->Connect('google.com', 80); $self->EnableReadLine; $self->Write("$line\r\n"); } sub OnReadLine { my ($self, $line) = @_; $line =~ s/[\r\n]//g; $self->GetModule->PutStatus($line); } package testmodule; use base 'ZNC::Module'; sub OnModCommand { my ($self, $cmd) = @_; my $sock = $self->CreateSocket('testmodule::connsock', "GET $cmd HTTP/1.0\r\n"); } 1;
# testmodule2.pm package testmodule2::conn; use base 'ZNC::Socket'; sub OnReadLine { my ($self, $line) = @_; $line =~ s/[\r\n]//g; $self->GetModule->PutStatus($line); } package testmodule2; use base 'ZNC::Module'; sub OnModCommand { my ($self, $cmd) = @_; my $sock = $self->CreateSocket('testmodule2::connsock'); $sock->Connect('google.com', 443, ssl=>1); $sock->EnableReadLine; $sock->Write("GET $cmd HTTP/1.0\r\n\r\n"); } 1;
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.
# testmodule3.pm package testmodule3::listensock; use base 'ZNC::Socket'; sub OnAccepted { my ($self, $host, $port) = @_; $self->GetModule->CreateSocket('testmodule3::accepted', $host, $port); } package testmodule3::accepted; use base 'ZNC::Socket'; sub Init { my ($self, $host, $port) = @_; $self->Write("Hello, $host:$port!\n"); } sub OnReadData { my $self = shift; my ($data, $len) = @_; $self->Write($data, $len); # echo back everything } package testmodule3; use base 'ZNC::Module'; sub OnLoad { my $self = shift; my $sock = $self->CreateSocket('testmodule3::listensock'); my $port = $sock->Listen(ssl=>1, addrtype=>'ipv6'); if ($port) { $_[1] = "Listening on all IPv6 interfaces on port $port using SSL"; } 1 } 1;
Sockets can override following callbacks:
Init
- is called fromCreateSocket
, first argument is reference to socket, the rest is from arguments toCreateSocket
.OnConnected
OnDisconnected
OnTimeout
OnConnectionRefused
OnReadData
- gets 2 arguments (of course, excluding reference to self): data and number of bytes.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 undef if you don't need the connection, or reference to new socket, which will be used for this connection.OnShutdown
- (since 0.097) destructor of the socket.
If callback On*
dies/croaks, the socket is closed, but if you want to close socket, use method Close
instead. If Init
croaks, 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 - http://znc.sourceforge.net'
Check ZNC C++ documentation for details.
Since ZNC 0.097, for getting ZNC version from Perl modules, you can use following functions:
ZNC::GetVersion() # For example, number 0.097 ZNC::GetVersionMajor() # 0 ZNC::GetVersionMinor() # 97 ZNC::GetVersionExtra() # build-specific string
Perl module Examples
Here is a listing of useful perl modules