-
Notifications
You must be signed in to change notification settings - Fork 6
Guide
DESCRIPTION
LOADING AND UNLOADING SCRIPTS
File Locations
Autorunning Scripts
Testing
Loading
Unloading
ANATOMY OF A SCRIPT
Preamble
COMMONLY SCRIPTED TASKS
Modifying an input line before sending
Responding to a public message
Responding to a private message
USEFUL THINGS
Dealing with Blocking IO
Getting the Response Value of a Remote Command
Getting the Response Value of a Local Command
Sharing Code Between Scripts
Using Signals
Using Functions
Checking if a script is loaded
Calling Functions in Other Scripts
If In Doubt, Dump!
Making Scripts Look Native
Provide Help
Use Tab Completion
Use Settings for Customisation
Use Subcommands to Group Script Functionality
Use Existing Formats for Consistency
Creating a new Statusbar Item
Creating a new Expando
THANKS
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.
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/.
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
SCRIPT LOAD script1
SCRIPT LOAD script2
SCRIPT LOAD script3into 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.
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-and
/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 argumentsa 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|http://perldoc.perl.org/perlop.html#Quote-and-Quote-like-Operators]] 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|Irssi#command_bind_$cmd,_$func,_$category]] 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 nickcolor.pl becomes /SCRIPT UNLOAD nickcolor.
As part of the unloading process, if the script contains a
sub UNLOAD {
...
}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.
TODO: Figure out a basic script to use as an example
All scripts should contain a header as follows:
use strict;
use warnings;
use vars qw($VERSION %IRSSI);
use Irssi;
$VERSION = '1.00';
%IRSSI = (
authors => 'Author Name(s)',
contact => '[email protected]',
name => 'Script Title',
description => 'Longer script description ',
license => 'Public Domain',
);The use vars qw($VERSION %IRSSI) defines two global variables, which are then set below to the appropriate values.
use Irssi tells the script to make the various Irssi support functions available. Additional parameters passed here with qw/.../ can be used to import functions into the current script namespace.
The %IRSSI hash is not used directly by irssi, but scripts such as scriptassist.pl can use this information to help keep track of script versions and notify you of upgrades.
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);
}
}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.
TODO: catch "messsage private", check params, generate response
See https://github.com/shabble/irssi-scripts/blob/master/feature-tests/pipes.pl 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.
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 cmpusers.pl (unpublished, afaik) by Bazerka <[email protected]>.
# He is not to blame for any problems, contact <[email protected]> instead.
use strict;
use warnings;
use Irssi;
my $running = 0; # flag to prevent overlapping requests.
sub redir_init {
# set up event to handler mappings
Irssi::signal_add
({
'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) = @_;
$server->redirect_event
(
'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.",
MSGLEVEL_CLIENTCRAP);
$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) {
Irssi::active_win->print
("test_redir error: a request is currently being processed "
. "- please try again shortly.", MSGLEVEL_CLIENTCRAP);
return;
}
$running = 1;
request_whois($server, $nicks[0]);
}
redir_init();
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.
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;
do_capture();
$win->print("buffer is: $buffer");
$buffer = '';
}
sub sig_print_text {
my ($text_dest, $str, $stripped_str) = @_;
$buffer .= $stripped_str;
Irssi::signal_stop;
}This snippet was condensed from [F]
In theory, it might be possible to look up the expected formats for the data in question, and use those as a more reliable template for parsing, but this is significantly nontrivial.
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 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:
script_a.pl
...
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
sub script_is_loaded {
return exists($Irssi::Script::{shift(@_) . '::'});
}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.
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.
An alternative, and most likely better approach, is to use the can method provided by the UNIVERSAL base class, in the following manner:
sub call_other_script {
my ($script_package, $function_name, @args) = @_;
$script_package->can($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') {
$coderef->(@args);
} else {
# The function does not exist
}UNIVERSAL->can()-E<g>() was sugested by LeoNerd on Freenode/#perl.
Additional documentation is available at http://perldoc.perl.org/UNIVERSAL.html.
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.
....
}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-])->DumpA slight variation on the theme, is:
/alias DUMP script exec use Data::Dumper\; use Irssi (@Irssi::EXPORT_OK)\;
print Data::Dumper->new([\\$0-])->DumpThis allows you to refer to functions in the Irssi namespace without having to prepend Irssi:: each time.
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.
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);
Irssi::signal_stop;
}
}
);/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.
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_stop;
}
Irssi::signal_add_first('complete word', \\&do_complete);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.
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.
TODO: different types of settings
TODO: register/set/get
TODO: Listening for changes and acting accordingly
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:
Irssi::command_bind("foo bar", \\&subcmd_bar);
Irssi::command_bind("foo", \\&subcmd_handler);
sub subcmd_handler {
my ($data, $server, $item) = @_;
$data =~ s/\s+$//g; # strip trailing whitespace.
Irssi::command_runsub('foo', $data, $server, $item);
}
sub subcmd_bar {
my ($args) = @_;
print "subcommand called with: $args";
}Unfortunately, the printformat() functions provided in the [Irssi|irssi]], 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);
}
}
Irssi::active_win->actually_printformat(Irssi::MSGLEVEL_CRAP, 'fe-common/core',
'window_name_not_unique'); # takes no args.A default irssi instance contains 4 statusbars:
prompt-
The bottom-most line of the terminal, which contains the prompt, and the user input widget.
Note: Whilst it is possible to create multiple
inputstatusbar-items, things will break horribly if you do. So don't. window-
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.
window_inact-
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. topic-
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 {
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');Once this code is loaded, you can add the item with a command such as:
/STATUSBAR window ADD foo_sb. There are parameters you can pass to /STATUSBAR to gain finer control over where exactly it is placed. See /HELP STATUSBAR for details.
See also Irssi#Expandos and Formats#EXPANDOS_(SPECIAL_VARIABLES)
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.
Hey! The above document had some coding errors, which are explained below:
- Around line 331:
-
Unknown E content in E<g>
Much of the content on these pages is taken from original Irssi documentation and is Copyright © 2000-2010 The Irssi project. Formatting and additional documentation, examples, etc by Tom Feist and the other editors of this wiki. This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 License. Please see http://creativecommons.org/licenses/by-sa/2.5/ for details.