ailin-nemui edited this page Sep 24, 2014 · 90 revisions

Guide To Irssi Scripting.

home |


This page presents a bunch of additional information about scripting for Irssi that doesn't fit well anywhere else. It contains useful tips, common pitfalls, and a bunch of other handy things. At least, I hope so.


File Locations

Packaged Irssi script files are usually placed in /usr/share/irssi/scripts/, but custom scripts or those required by a single user can be placed in ~/.irssi/scripts/.

Autorunning Scripts

If you require a script be run when Irssi starts, you can place the file (or better, create a symlink to it) into ~/.irssi/scripts/autorun/.

Alternatively, if you want more control over the order in which scripts are autoloaded, you can place


into your ~/.irssi/startup file.

This tip was provided by Rhonda on Freenode/#irssi.

Of course, you could just rename your symlinks to ensure your scripts load in lexicographical order. But some might consider that cheating.

Note: The ~/.irssi/startup file runs before any scripts in the scripts/autorun/ directory are loaded. So if you want to run commands provided by a script in your startup file, you need to load them from there, before the actual command.

Script Names

The names Irssi gives to your scripts are based on the filename in which the script resides. Because the name is also used as the Perl package, it is slightly mangled from the filename.

There are three identifiers associated with a script:

  • Filename

  • Irssi Internal Name

  • Script Package Name


The SCRIPT EXEC command allows you to test various simple ideas straight from the Irssi interface. It can also be used to register signal handlers and commands if run with the -permanent option.

Note: The -permanent flag only means that the script should not terminate immediately. It is not persistent between restarts of the Irssi client. Truly permanent scripts should be placed in autorun scripts or added to ~/.irssi/startup

The script exec command can be very useful for testing out little snippets of code, especially when combined with the /DUMP alias described below.

One useful trick is to define an alias such as:

/ALIAS SE SCRIPT EXEC use Irssi (@Irssi::EXPORT_OK)\; $0-


/ALIAS SEP SCRIPT EXEC -permanent use Irssi (@Irssi::EXPORT_OK)\; $0-

These allow you to quickly prototype ideas, such as:

/SE active_win->print join ", ", map { $_->{tag} } servers()

to access the list of currently connected servers, etc.

If you wish to bind commands, add signal handlers, or use timeouts, you must use the -permanent form of the command, otherwise the code will be destroyed before it ever has a chance to run.

Using SCRIPT EXEC in conjunction with aliases can make for small (or not-so-small) "scriptlets", which you can use in everyday operation.

Note: When wrapping scriptlets in aliases, you must be aware of the escaping rules necessary. Aliases treat ; as a command separator, so you must escape it within your script as \;. Variable ($ symbols must also be protected, either with a backslash escape, or by doubling them up ($foo becomes $$foo, etc. You must also be careful about where your actual alias arguments are going to be inserted. If you do not specify them anywhere, they will be added at the end in the form $0-.

Terminating your scripts with \; can protect them from arguments breaking them, but the most interesting script(lets) use their arguments for something. This can lead to problems, since no matter how you try to escape it, there will always be some form of input which the script cannot handle. For example:

/ALIAS FOO SCRIPT EXEC print 'You said %_$0%_'\;

This works fine, until the input contains a ' character, at which point everything breaks. Judicious use of Perl's weird and wonderful quoting rules can minimise this problem, by picking a delimiter unlikely to appear in common usage, but there is always the change of it going wrong.

Should you find yourself in this situation, the only real option is to use an actual command, via the command_bind function. This is around the point it starts becoming worth writing a script, rather than ad-hoc commandline-pokery, however.


Scripts are loaded via /SCRIPT LOAD filename. A default Irssi configuration also provides the /RUN alias as an alternative to /SCRIPT LOAD.

Loaded scripts will exist in the Irssi namespace as: Irssi::Script::<name>, where name is the filename stripped of its .pl extension.


A script can be unloaded via the /SCRIPT UNLOAD name command. The name is typically the script filename without the .pl extension, so becomes /SCRIPT UNLOAD nickcolor.

As part of the unloading process, if the script contains a

  sub UNLOAD {
function, it will be run just before the script is unloaded and all variables destroyed. This can be used to clean up any temporary files, shut down any network connections or processes, and restore any Irssi modifications made.


In this section, we develop a very simplistic script and look at the necessary code.

Note: This section has considerable overlap with Juerd's Scripting Tutorial, which you may also wish to read.

A basic skeleton of a script with the preamble and documentation stubbed in called is a good starting point for new scripts.


The preamble of a script contains certain metadata for the script, declared as a package variable (via use vars qw/$var/; or our $var;.

The specification for the various fields of the header can be found on the Header page. Note that not all scripts use all the parameters, and sometimes include additional ones.

All scripts should contain a header as follows:

  use strict;
  use warnings;

  # This is a common method of declaring package scoped variables before the
  # 'our' keyword was introduced.  You should pick one form or the other, but
  # generally speaking, the our $var is preferred in new code.

  #use vars qw($VERSION %IRSSI);

  use Irssi;

  our $VERSION = '1.00';
  our %IRSSI = (
      authors     => 'Author Name(s)',
      contact     => '',
      name        => 'Script Title',
      description => 'Longer script description, '
                  .  'maybe on multiple lines',
      license     => 'Public Domain',

The first two lines are optional, but strongly advised. They provide far greater error checking and diagnostics should you make a mistake in your code.

The use vars qw($VERSION %IRSSI) defines two global variables, which are then set below to the appropriate values. As described in the comment above, the our $VERSION = '0.1'; form is preferred.

use Irssi tells the script to make the various I‌rssi support functions available. Additional parameters passed here as a list with qw/.../ or ('...', '...', ...) can be used to import functions into the current script namespace.

The %IRSSI hash is not used directly by irssi, but scripts such as can use this information to help keep track of script versions and notify you of upgrades.

Accessing Script Metadata From Outside Irssi

In some rare situations, you may wish to access the metadata hash or version from a program external to Irssi. The module Irssi::Script::InfoParser allows this to be performed with a reasonable level of success, depending on the complexity of the hash structure.

The script is not itself loaded, but rather parsed using the PPI libraries, and relies to some degree on pattern-matching to extract the metadata. Complex values, such as the return values from functions, or anything more complex than string concatenation are unlikely to be parsed correctly.

Checking for connection type

When using some generic functions, you should check whether you are dealing with an actual IRC connection. Irssi with plugins supports some other networks like SILC, XMPP, etc. and if your script is IRC-specific you should check for connection type like this:

 sub server_connected {
    my $server = shift;
    if ($server->{chat_type} eq "IRC") {
         $server->send_raw_now("This is IRC specific");
 Irssi::signal_add_first('server connected', \&server_connected);

Otherwise you may get messages like (as described in the irssi-xmpp FAQ):

 Can't locate object method "xxx" via package "Irssi::Xmpp::Server"


Modifying an input line before sending

For any number of reasons, you might want to capture an input line before it is sent to the appropriate channel or query. You might want to censor bad words, remove sensitive information, or just clean up your spelling. This is quite a simple task, as the following example shows:

 Irssi::signal_add_first 'send text', 'my_handler';

 sub my_handler {
    my ($text, $server, $win_item) = @_;
    if ($text =~ m/My Secret Password/) {
        Irssi::signal_stop(); # prevent it from being sent at all

    if (contains_bad_words($text)) {
        $text = filter_words($text);
        # continue is necessary since you changed the contents of the signal
        # parameters.
        Irssi::continue($text, $server, $win_item);

    if ($text =~ s/teh/the/g) {
       # continue as before
       Irssi::signal_continue($text, $server, $win_item);

Responding to a public message

You might want to create a very simple "bot" type script, which responds to certain strings said to it.

 Irssi::signal_add 'message public', 'sig_message_public';

 sub sig_message_public {
     my ($server, $msg, $nick, $nick_addr, $target) = @_;
     if ($target =~ m/#(?:chanA|chanB)/) { # only operate in these channels
          $server->command("msg $target $nick: Hi There") if ($msg =~ m/hello/i);

Note: Caution is advised when setting up bots which automatically respond to certain inputs. They can be abused by malicious users, and sometimes end up getting stuck in loops with other bots in which they trigger each other incessantly.

Responding to a private message

TODO: catch "messsage private", check params, generate response


Fun With Window(item) Contents

The hierarchy of objects needed to represent a given line of text is quite complex. There are a few major players:


Inserting Lines

 my $win = Irssi::active_win();
 my $win_viewbuffer = $win->view()->{buffer};

 # The very first line in the buffer.
 my $line = $win_viewbuffer->{first_line};

 $win->print_after($line, Irssi::MSGLEVEL_CLIENTCRAP, "I love horses");

Removing Lines


Dealing with Blocking IO

See for a minimalist example of script which uses forking and the input_add features to ensure that the main Irssi instance does not block whilst reading from some other source, such as a network socket.

Getting the Response Value of a Remote Command

Irssi supports event redirections, which are a powerful mechanism for capturing specific server output for use in scripts.

The specific functions are documented in Irssi::Irc::Server::redirect_event.

A fairly long example demonstrates how you can capture data from the whois command:

 # mangled from (unpublished, afaik) by Bazerka <>.
 # He is not to blame for any problems, contact <> instead.

 use strict;
 use warnings;

 use Irssi;

 my $running = 0; # flag to prevent overlapping requests.

 sub redir_init {
    # set up event to handler mappings
          'redir test_redir_whois_user'       => 'event_whois_user',
          'redir test_redir_whois_channels'   => 'event_whois_channels',
          'redir test_redir_whois_end'        => 'event_whois_end',
          'redir test_redir_whois_nosuchnick' => 'event_whois_nosuchnick',
          'redir test_redir_whois_timeout'    => 'event_whois_timeout',

 sub request_whois {
    my ($server, $nick) = @_;

       'whois', 1, $nick, 0,             # command, remote, arg, timeout
       'redir test_redir_whois_timeout', # error handler
        'event 311' => 'redir test_redir_whois_user', # event mappings
        'event 318' => 'redir test_redir_whois_end',
        'event 319' => 'redir test_redir_whois_channels',
        'event 401' => 'redir test_redir_whois_nosuchnick',
        ''          => 'event empty',
    Irssi::print("Sending Command: WHOIS $nick", MSGLEVEL_CLIENTCRAP)
      if $debug;
    # send the actual command directly to the server, rather than
    # with $server->command()
    $server->send_raw("WHOIS $nick");

 sub event_whois_user {
    my ($server, $data) = @_;
    my ($nick, $user, $host) = ( split / +/, $data, 6 )[ 1, 2, 3 ];
    Irssi::print("test_redir whois_user: $nick!$user\@$host", MSGLEVEL_CLIENTCRAP);

 sub event_whois_channels {
    my ($server, $data) = @_;
    my ($nick, $channels) = ( split / +/, $data, 3 )[ 1, 2 ];
    Irssi::print("test_redir whois_channels: $nick, $channels", MSGLEVEL_CLIENTCRAP);

 sub event_whois_end {
    my ($server, $data) = @_;
    my ($nick) = ( split / +/, $data, 3 )[1];
    Irssi::print("test_redir whois_end: $nick", MSGLEVEL_CLIENTCRAP);

    return unless $running; # catch 318 -> 401 (nosuchnick followed by endofwhois)
    $running = 0;

 sub event_whois_nosuchnick {
    my ($server, $data) = @_;
    my $nick = ( split / +/, $data, 4)[1];
    Irssi::active_win->print("test_redir error: no such nick $nick - aborting.",
    $running = 0;

 sub event_whois_timeout {
    my ($server, $data) = @_;
    Irssi::print("test_redir whois_timeout", MSGLEVEL_CLIENTCRAP);
    $running = 0;

 sub cmd_test_redir {
    my ($args, $server, $witem) = @_;
    $args = lc $args;
    my @nicks = split /\s+/, $args;

    if ($running) {
            ("test_redir error: a request is currently being processed "
             . "- please try again shortly.", MSGLEVEL_CLIENTCRAP);
    $running = 1;
    request_whois($server, $nicks[0]);

 Irssi::command_bind("test_redir", \&cmd_test_redir);

This snippet was mutilated from an original script provided by Bazerka on Freenode/#irssi.

Note: In order to use the redirection features, the specific command must be registered with Irssi. A number of common functions (listed at the bottom of Irssi::Irc::Server::redirect_register) are pre-registered by Irssi at startup, but otherwise you may need to use the redirect_register method yourself.

Getting the Response Value of a Local Command

There is no simple way to achieve this. The actual data backing most common commands may be extractable through standard API calls, but there are still quite a large number of exceptions.

The following snippet demonstrates one way to call a command, and capture the printed output into a variable. This may suffice for some purposes, but due to the configurability of themes and formats, parsing it consistently across clients may prove tricky.

 use strict;
 use warnings;

 use Irssi;

 my $buffer = '';

 Irssi::command_bind 'showbuf', \&cmd_showbuf;

 sub do_capture {
    my $cmd = shift;
    Irssi::signal_add_first 'print text', 'sig_print_text';
    Irssi::command $cmd;
    Irssi::signal_remove 'print text', 'sig_print_text';

 sub cmd_showbuf {
    my ($args, $server, $win_item) = @_;
    my $win = $win_item ? $win_item->window : Irssi::active_win;


    $win->print("buffer is: $buffer");
    $buffer = '';
 sub sig_print_text {
    my ($text_dest, $str, $stripped_str) = @_;

    $buffer .= $stripped_str;

This snippet was condensed from by Timo 'cras' Sirainen and Wouter Coekaerts.

Making Parsing Easier

Because the only way to access certain information is to capture it as it would be normally printed to the display, it is already rendered using specific format. This can lead to a loss of information (for example, when the $[width]varname is used), or fail to parse at all, if the user, their scripts, or their theme, has altered the default formats.

To get around this, the format is temporarily altered to a parse-friendly version, the data printed, and then the format reverted.

The current format template can be extracted from the theme object via Irssi::current_theme->get_format($module, $tag).

The link above gives a list of the possible modules. The $tag variable is the name of the format itself, such as chansetup_header or script_not_loaded.

Once the original formats have been preserved, the next step is to alter the format to something easier to process.

my $parse_line_format = "channel:\$0\tnet:\$1\tpass:\$2\tsettings:\$3";
Irssi::command("^FORMAT chansetup_line $parse_line_format");

This sets the chansetup_line format to a sequence of key:value pairs, separated by tab characters. This can be processed by a regular expression, or by something like:

 sub sig_print_text {
    my ($text_dest, $str, $stripped_str) = @_;

    if ($text =~ m/channel:([^\t]+)\tnet:([^\t]+)\tpass:([^\t]*)\tsettings:(.*)$/) {


Irssi generally defines a *_header and *_footer format for data that are presented as a list or table. Setting these to known values such as 'START' and 'END' makes determining the end of the parse trivial.

At this point, the formats are then restored to their original values, and the signal handler on 'print text' is removed. provides a complete working example which extracts saved /CHANNEL list names, associated network, password, and any options such as autojoin.


There are 2 main ways for scripts to communicate, either via emitting and handling Irssi signals, or by calling functions from one another directly.

In general, the "proper" way to implement communication between scripts is through the use of custom registered signals. The latter approach of hunting around inside another scripts package for functions is more likely to cause problems, and should really only be used if there is a compelling reason (such as performance, if the inter-script communication is your bottleneck).

Using Signals

In order to use custom signals, they must first be registered with Irssi. During registration, a list of the parameters must also be specified. Once specified, it cannot be changed without restarting Irssi, so be warned.

After registration, your script can simply listen for signals with Irssi::signal_add, or generate them for others to handle with Irssi::signal_emit

For example:

 Irssi::signal_register({'my signal' => [qw/string/]});
 Irssi::signal_add('my signal',
                  sub { my $arg = shift; print "I got my signal with $arg" });
 Irssi::signal_emit('my signal', 'hello from script B!');

Note: The signal can only be generated after script_a has loaded, so the signal is registered

Using Functions

Checking if a script is loaded

 sub script_is_loaded {
    return exists($Irssi::Script::{shift(@_) . '::'});

Calling Functions in Other Scripts

Because scripts exist in a well-defined namespace of Irssi::Script::SOMEPACKAGE, it is possible to manipulate the perl symbol table to call functions directly on them, assuming they are loaded.

Assuming the script Irssi::Script::NAME is loaded, and does indeed possess a func function, the following code can be used to call it.

 sub call_other_script {
    my ($script_package, $function_name, @args) = @_;

   # Additional safety can be achieved checking the reference is valid

   my $coderef = $script_package->can($function_name);
   if (defined $coderef and ref $coderef eq 'CODE') {
   } else {
      # The function does not exist

UNIVERSAL->can()->() was suggested by LeoNerd on Freenode/#perl.

Additional documentation is available at

An alternative, but messier, approach is to use the qualify_to_ref() function from the Symbol package.

 use Symbol qw/qualify_to_ref/;

 my $script_package = "Irssi::Script::NAME";
 my $function_name = "func";
 my @args = ('a' .. 'z');
 *{qualify_to_ref($function_name, $script_package)}{CODE}->(@args);

See Symbol for further details.

In both cases, there may be some situations under which the function call could fail (for example, the target script being unloaded at exactly the same time the call is made. For that reason, it would be sensible to guard each call like:

 my $result =
 eval {
    call_other_script($pkg, $func, @args);

 if ($@) {
    # some error has occurred, and $result will be unreliable.

If In Doubt, Dump!

Data::Dumper is an extremely good way to inspect Irssi internals if you're looking for an undocumented feature.

The DUMP alias by Wouter Coekaerts provides an easy way to check object fields.

Dump perl object (e.g. /dump Irssi::active_win):

/alias DUMP script exec use Data::Dumper\; print Data::Dumper->new([\$0-])->Dump

A slight variation on the theme, is:

/alias DUMP script exec use Data::Dumper\; use Irssi (@Irssi::EXPORT_OK)\;
print Data::Dumper->new([\$0-])->Dump

This allows you to refer to functions in the Irssi namespace without having to prepend Irssi:: each time.

Making Scripts Look Native

An important part of creating a good script is to make it behave as though it were a part of Irssi. Adhering to some of the standard conventions can make this easier.

Provide Help

Scripts commonly store information about how to use them in comments at the top of their file. Whilst better than no documentation at all, a preferable approach is to allow that help to be accessed from within Irssi itself, using the /HELP command.

  my $help = "this is help for b";

  Irssi::command_bind('help', sub {
          if ($_[0] eq 'test_b') {
              Irssi::print($help, MSGLEVEL_CLIENTCRAP);
This example demonstrates overriding the /HELP command, and if the argument matches our command, print some custom help and prevent the internal Irssi help function from being called. Otherwise, it falls back to the default help.

This snippet was provided by culb on Freenode/#irssi.

Use Tab Completion

One of the great features of Irssi is the ability to complete commands, subcommands and even certain arguments. Using the subcommands processing feature described below automatically allows those subcommands to be tab-completed, but for more complex tasks, you can hook into the autocompletion system itself.

In order to create your own completions, you must intercept the complete word signal and return a list of completion candidates.

  sub do_complete {
      my ($strings, $window, $word, $linestart, $want_space) = @_;

      # only provide these completions if the input line is otherwise empty.
      return unless ($linestart eq '' && $word eq '');

      # add the completion candidates
      push @$strings, qw/foo bar baz bacon/;

      # indicate that we want to include a space after the completion
      $$want_space = 1;

      # prevent internal Irssi completion from responding

  Irssi::signal_add_first('complete word',  \&do_complete);

This snippet taken from F by Daenyth

Use Settings for Customisation

Many scripts require the setting of various parameters to affect how they behave. One approach is to require the user to directly edit the script file, but this is less than ideal for a number of reasons. Firstly, it is easy to introduce errors into a script by accidentally deleting closing quotes or semicolons. Secondly, it has no effect until the script is reloaded, leading to confusion.

A much better alternative is to use Irssi's inbuilt settings mechanism to allow users to set these parameters from within Irssi, as well as to /SAVE their settings for subsequent invocations.

See Irssi/Settings for the full details of what setting data-types are available.

Detecting Changes in Settings

my $str_cache;

Irssi::settings_add_str("myscript", "my_str_var", "some default");

Irssi::signal_add('setup changed', \&reload_settings);

sub reload_settings {
    $str_cache = Irssi::settings_get_str('my_str_var');


sub do_something {
    # if we change the value, other things may need to
    # know
    Irssi::settings_set_str('my_str_var', $something);
    Irssi::signal_emit('setup changed');

In the example above, a string setting is first registered, and given a category and default value. The default value is only applied when the setting is first added, or via /SET -default $var.

A signal handler is then registered to listen for config changes, and to re-read the cached value of our variable when signalled.

The setup changed signal does not provide any indication of what changed, so all settings must be checked when the signal arrives.

The last section of the example, the do_something() function, changes the value of our setting, and then triggers the signal to ensure all other code is aware of the change.

There are 6 types of settings; str - a standard string type, int - a signed 32-bit integer type, bool, a boolean type, time - a time-duration type, expressed as a signed 32-bit integer number of milliseconds, level - a specialised string type, accepting a space-separated list of message-level names optionally prepended with a + or -, and size - a type representing a number of bytes, typically in the form of a number followed by a suffix such as "g[bytes]" or "k[bytes]".

See Irssi/Settings for more comprehensive information.

Use Subcommands to Group Script Functionality

A common theme in Irssi scripts is to define commands with a prefix, such as /myscript_foo, myscript_bar, etc. This helps to avoid accidentally clobbering native commands and those defined by other scripts, but is a problem better solved with subcommands.

Subcommands allow you to bind commands such as /myscript foo and /myscript bar. Completions are automatically handled for both the primary command, and any subcommands contained within it.

The following example demonstrates how to use subcommands from within a script:

  my $BASE_CMD = 'foo';
  Irssi::command_bind("$BASE_CMD bar", \&subcmd_bar);
  Irssi::command_bind($BASE_CMD, \&subcmd_handler);
  Irssi::signal_add_first("default command $BASE_CMD", \&subcmd_unknown);

  sub subcmd_unknown {
    # gets triggered if called with unknown subcommand
      Irssi::signal_stop(); # avoid 'no such command' error.

  sub subcmd_handler {
      my ($data, $server, $item) = @_;
      $data =~ s/\s+$//g; # strip trailing whitespace.
      Irssi::command_runsub($BASE_CMD, $data, $server, $item);

  sub subcmd_bar {
      my ($args) = @_;
      print "subcommand called with: $args";

Use Existing Formats for Consistency

Unfortunately, the printformat() functions provided in the I‌rssi, Irssi::Server, Irssi::UI::Window, and Irssi::Windowitem objects are written in such a way that they can only access formats created by the scripts that contain them.

This is done by checking their package of origin using the perl caller() function. The following function demonstrates an example of subverting the caller function to allow it access to formats in other modules.

The $module parameter corresponds to the module the format is stored in. A full listing of module names and their formats is here.

 sub actually_printformat {
    my ($win, $level, $module, $format, @args) = @_;
        # deeeeeeep black magic.
        local *CORE::GLOBAL::caller = sub { $module };
        $win->printformat($level, $format, @args);


 my $window = Irssi::active_win;
 actually_printformat($window, Irssi::MSGLEVEL_CRAP, 'fe-common/core',
                     'window_name_not_unique'); # takes no args.

Creating a new Statusbar Item

A default irssi instance contains 4 statusbars:


The bottom-most line of the terminal, which contains the prompt, and the user input widget.

Note: Whilst it is possible to create multiple input statusbar-items, things will break horribly if you do. So don't.


The bottom border of the active window, containing information about that window and its windowitems, as well as perhaps a clock, and activity notification bar.


The equivalent to window, but for the inactive window if splits are being used. It contains less information, and is usually dulled or de-hilighted in some way to indicate that it is inactive.


The top bar of each window contains the channel topic if the window has an active channel windowitem, the userhost if a query, or the Irssi version string if the window is empty of all items.

In order to create a statusbar item, there are two main procedures; firstly, write the code to register it with irssi and provide a drawing function, and secondly, to add it to one of the statusbars listed above so that it is visible.

The following example shows a drawing function, and the register function which links it to the name of the item, in this case, foo_sb.

 use Irssi;
 use Irssi::TextUI;              # for sbar_items_redraw

 sub foo_sb_draw_handler {
    my ($sb_item, $get_size_only) = @_;
    my $sb = '%gmoo%n';

    # all draw functions should end with a call to default_handler().
    $sb_item->default_handler($get_size_only, "{sb $sb}", '', 0);

 Irssi::statusbar_item_register ('foo_bar', 0, 'foo_sb_draw_handler');

Once this code is loaded, you can add the item with a command such as:

/STATUSBAR window ADD foo_bar. There are parameters you can pass to /STATUSBAR to gain finer control over where exactly it is placed. See /HELP STATUSBAR for details.

Creating a new Expando

See also Irssi/Expandos and Formats/EXPANDOS

The following example demonstrates how to create a new expando (in this case, named $wins), which expands to the current number of windows open.

 Irssi::expando_create 'wins' => sub {
    my ($server, $witem) = @_;
    my @wins = Irssi::windows;
    scalar @wins
 }, {};

This snippet provided by mauke@Freenode/#irssi.


This page draws on the help of many many people. Individuals have been named where possible for their contributions, as well as the #irssi hive-mind :)

The denizens of Freenode/#irssi have been particularly helpful, especially Bazerka and culb.