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

From ZNC
Jump to navigation Jump to search
Line 222: Line 222:
</pre>
</pre>
=== Timers ===
=== Timers ===
There're 2 APIs: first uses references to subs, second uses OOP packages.
==== Ref API ====
Use helper function <code>CreateTimer</code>.
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.
<pre>
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");
}
</pre>
==== OOP API ====
''(available since ZNC 0.097) ''
Use helper function <code>CreateTimer</code>. It gets following named arguments:
task ''(required)'' - name of your Timer class. It should be derived from <code>ZNC::Timer</code>. You can override 2 methods: <code>RunJob</code> and <code>OnShutdown</code>.
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.
<pre>
# 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';
}
</pre>
You can use methods of C++ class <code>CTimer</code> (like Stop) for your timer.
=== Sockets ===

Revision as of 17:39, 17 December 2011

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.

To build modperl, 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/modperl/ (ZNC.pm and modperl.i should be in the same dir), and add option --disable-swig to ./configure

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 user 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 have several perl packages inside your module, you should have 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.

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

As you see, to get normal string from ZNC::String there's a method GetPerlStr. 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!"
    }
}

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

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

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";
# 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 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. 
# 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