Basic Perl Script

Writing a simple XChat Perl Script

XChat allows users to script in several languages, it does not restrict script writers to only one language, and it especially doesn't restrict scripts to a language not used by other applications. The most used languages by XChat scriptors are Perl, Python, and TCL. If you know any of these languages, you should be able to follow the API Documentation for each and be able to program an XChat script. Even so, it may take a bit to get used to the quirks of whatever language.

In this tutorial, I will go through the process of creating a simple script using the XChat Perl Interface using the example of an auto response script. While this page will be suitable for scriptors with only a limited knowledge of Perl, you cannot learn Perl just from this page. For that, I recommend going to an external site, such as or the book Beginning Perl which you can read online, or buy in paper. You can also find many results with a simple Google Search. Instead, this How To will deal with the basics of using Perl with XChat. (Note, there is another document describing how to use IRC:: with XChat, rather than Xchat::, but the IRC:: XChat interface is out of date and deprecated, so it is recommended you do NOT use this older interface.)

A Basic XChat Perl Script Tutorial

Following, you will be given a very basic script with the different parts explained. After each part has been explained, the script will be modified using other features of the XChat Perl API, then rinsed, and repeated. The examples provided serve no useful purpose, but serve as adequate material for a tutorial on using the XChat Perl API.

  1. use strict;
  2. use warnings;
  4. Xchat::register('Oink Script', '0.1', 'Oink at the channel');
  6. Xchat::hook_print('Channel Message', \&check_oink);
  8. sub check_oink {
  9.         if ($_[0][1] =~ /oink/i){
  10.                 Xchat::command("me oinks");
  11.         }      
  12.         return Xchat::EAT_NONE;
  13. }

Lines 1-2) use strict; and use warnings; aren't absolutely necessary for writing a Perl script, however they do make it easier to spot potential problems at an early stage. As such, it is good practice to start out with these two lines.

Line 4) Register the script with the XChat plugin system. You don't really need this, but it allows you to distinguish the script automatically. The primary parameters are: 'Script Name', 'version string', 'Description'

Line 6) Hook all "Channel Message" Text Events and send them to the check_oink sub (you can see the possible events in Settings -> Advanced -> Text Events). As such, you use the litteral string 'Channel Message', not the text you are looking for. The \&check_oink is a reference to the sub check_oink, and the \& designates it as such. While the plugin does allow for passing a string that contains the sub's name, this way is not as efficient, and the reference is much preferred.

Line 9) Check for the string "oink" case insensitively to be inside of $_[0][1]. From the Perl Interface, you can see that $_[0] is an array reference of the options shown in the Text Events screen. The second item in the event is "The text", which corresponds to the second item in the $_[0] array, or $_[0][1].

Line 10) Issue the command me oinks in the current context (where the event happened). This would be the same as if you had done /me oinks in the channel.

Line 12) Xchat::EAT_NONE tells XChat to continue as normal, for this script, you don't care if some other script deals with this text, and you still want it to be displayed in channel. If you don't specify a return value, XChat will not display the print line. A non specified return value will return what Perl last evaluated, which in most cases is the same as returning Xchat::EAT_XCHAT, and this is many times not the desired behavior, so remember to return a specific EAT_* value.

There are a few issues with the above script:

  1. If someone were to type "Doink the Clown", the script would still oink instead of insulting the user for mentioning old WWF "Wrestlers".
  2. To you, it will look like you did the "me oinks" before the other person said oink. This isn't the case, but because the script hasn't finished at this point, XChat will perform the command before it continues, which would allow for the incoming text to be eaten before displayed, either by this script, or another.
  3. If you also happened to be highlighted in the oink line, you wouldn't respond.
  1. use strict;
  2. use warnings;
  3. use Xchat qw( :all );
  5. register('Oink Script', '0.2', 'Oink at the channel');
  7. foreach ('Channel Message', 'Channel Msg Hilight') {
  8.         hook_print($_, \&check_oink);
  9. }
  11. sub check_oink {
  12.         return EAT_NONE if ($_[0][1] !~ /^oink/i);     
  14.         delaycommand("me oinks");
  16.         return EAT_NONE;
  17. }
  19. # XChat 2.8.6 and newer
  20. sub delaycommand {
  21.         my $command = $_[0];
  22.         hook_timer( 0,
  23.                 sub {
  24.                         command($command);
  25.                         return REMOVE;
  26.                 }
  27.         );
  28.         return EAT_NONE;
  29. }

Line 3) use Xchat qw( :all ); means that you do not need to type Xchat:: before each command in the XChat Perl API.

Lines 7-8) As before, hook_print the event, but using the foreach, you could actually hook 8 events in 3 lines, instead of one line each. The $_ refers to each item in the list, first Channel Message, and then Channel Msg Hilight.

Line 12) Leave the sub now if the string doesn't start with "oink".

Line 14) Use the sub provided later on to issue a command, so that the "me oinks" isn't locally printed before the received event

Lines 20-29) This is the sub that allows a command to be done after the received event. The 0 is for a 0 millisecond delay to happen AFTER the current command process has finished. This is what allows for the incoming message to be printed before your reply.

The return REMOVE; is fairly vital to the hook_timer sub. Without it, the timer would continue in an infinite loop, repeatedly issuing the command. This behavior may change in the future, defaulting to REMOVE instead of KEEP, so it is best to specify what to do with the timer regardless of what the current default is.

The version of delaycommand given above only works for XChat 2.8.6 or newer, prior to this, hook_timer didn't remember which context it was issued in, and so you could end up with the command issued in the wrong channel, depending on the circumstance. To get around this, you could use the following sub instead (although, it would be better to upgrade):

delaycommand() for older versions
  1. # XChat 2.8.5 and older
  2. sub delaycommand {
  3.         my $context = get_context();
  4.         my $command = $_[0];
  5.         hook_timer( 0,
  6.                 sub {
  7.                         set_context($context);
  8.                         command($command);
  9.                         return REMOVE;
  10.                 }
  11.         );
  12.         return EAT_NONE;
  13. }

Next, we will modify the script to only oink if the person who issued the command is a halfop or higher in the channel. If it doesn't reply, it will print locally that it would have replied, except that the person who said "oink" is a peon.

  1. use strict;
  2. use warnings;
  3. use Xchat qw( :all );
  5. register('Oink Script', '0.3', 'Oink at the channel');
  8.         hook_print($_, \&check_oink);
  9. }
  11. sub check_oink {
  12.         my @words = split(/\s/, $_[0][1]);
  13.         return EAT_NONE unless(lc $words[0] eq 'oink');
  15.         my $userinfo = user_info($_[0][0]);
  17.         if ($userinfo->{prefix} && $userinfo->{prefix} ne '+') {
  18.                 delaycommand("me oinks");
  19.         }
  20.         else {
  21.                 hook_timer( 0, sub {
  22.                         prnt("No oink sent for peons");
  23.                         return REMOVE;
  24.                 });
  25.         }
  27.         return EAT_NONE;
  28. }
  29. # Same thing as before for XChat 2.8.6 and newer, just white space removed
  30. sub delaycommand {
  31.         my $command = $_[0];
  32.         hook_timer( 0, sub { command($command); return REMOVE; } );
  33.         return EAT_NONE;
  34. }

Lines 12-13) This is just another way to only reply if the first word is "oink". In version 0.2, the script would also reply if it sees "oinked individual" or "oinking". In version 0.3, there can be nothing after the "k" except either a space or the end of the line.

Line 15) Obtain a reference to a hash that contains information about the nick provided ($_[0][0]). This will be used in line 17. The nick is $_[0][0] because the Nick is the first item in the array of event data, as explained before.

Line 17) After obtaining the hash reference in step 13, this is how you would obtain what mode someone was given in the channel. $userinfo->{prefix} will return the highest ranking of a user in the channel, so if someone is both an op and a voice, $userinfo->{prefix} will return @

Lines 20-25) In the cases not covered by Line 17, do the section as described below.

Lines 21-24) This is done similarly to the delaycommand sub. This is just to show that you can do a hook_timer right in place. Like before, this sub will work best in 2.8.6 because of the timer not remembering which context it was issued in before 2.8.6. Remember to return REMOVE; inside of the timer sub, because this is considered as a separate sub from it's parent and so does need a return value of its own (or one may be provided for you that you do not like).

Line 22) This simply prints the string "No oink sent for peons". The prnt is short for Xchat::prnt, which is the same as Xchat::print. All of the above will type the text to the current context, but prnt was used instead of print, because "print" is a native function in Perl, but does not work as expected inside of XChat.

In the next version of the script, a command /oink will be added for you to use to issue an oink yourself. Also, a few other API commands will be explained.

  1. use strict;
  2. use warnings;
  3. use Xchat qw( :all );
  5. my $name = 'Oink Script';
  6. my $version = '0.4';
  8. register($name, $version, 'Oink at the channel');
  9. prnt("Loaded $name $version");
  11. foreach ('Channel Message', 'Channel Msg Hilight') {
  12.         hook_print($_, \&check_oink);
  13. }
  14. hook_command('oink', \&force_oink, { help_message => 'Usage: /oink [at] : Oink in the current context' });
  16. sub check_oink {
  17.         my ($first_word, $other_words) = split(/\s/, $_[0][1], 2);
  18.         if (uc $first_word ne 'OINK') {
  19.                 return EAT_NONE;
  20.         }
  25.                 delaycommand("me oinks");
  26.         }
  27.         else {
  28.                 hook_timer( 0, sub {
  29.                         prnt("No oink sent for peons");
  30.                         return REMOVE;
  31.                 });
  32.         }
  34.         if (find_context() == get_context()) {
  35.                 emit_print('Generic Message', '*INFO*', 'I am here to see the next line happen');
  36.         }
  38.         return EAT_NONE;
  39. }
  41. sub force_oink {
  42.         if ($_[1][1]) {
  43.                 command("me oinks at $_[1][1]");
  44.         }
  45.         else {
  46.                 command("me oinks in ".get_info('channel'));
  47.         }
  48.         return EAT_XCHAT;
  49. }
  51. sub delaycommand {
  52.         my $command = $_[0];
  54.         return EAT_NONE;
  55. }

Lines 5-8) Use variables for registering the script in XChat, just a change for maintenance sake.

Line 9) As soon as the script is loaded, print a message saying that it has been loaded.

Line 14) This is how to hook commands entered into the channel, in this case /oink [optional text]. The first parameter is the command to hook on, the second is the sub to execute when the command is received, and the third is a hash of options, where you can see how to add help text.

The Help Text will show up when you do /help oink or /help -l.

Lines 17-20) Yet another way to not continue if the first word of the line isn't "oink".

Line 34) With this line, you can check if what you are currently looking at (find_context()) is the same as where the text was received (get_context()). Only do the next line if you possibly could be looking at the channel.

Line 35) Using emit_print, you can print events in the same layout as specified in the Text Events window. The first parameter is the event name you want to emit, and after that, it accepts a list of strings that will be used to replace the $1, $2, etc. shown in the Event Text string.

This line will be printed before the received text and before the command issued earlier, even though it is later in the script. This is because the command to emit_print is issued NOW, rather than after the hook has completed.

Lines 41-49) The new sub that is called by Line 14

Line 42) Again from the Perl Interface Callback Arguments, you can see that for commands, there are three items that are passed to the sub by default, all inside of @_. As from the doc, you can see the $_[0] breaks up the command line into words, and that $_[1] is filled with an array where the first item has the entire string, and then progressively, words are removed from the beginning. So that means, issued the command /oink new person, $_[1][1] would be new person, and $_[1][2] would be person.

Line 46) The only new bit of this line is get_info('channel'). From the XChat Perl Documentation on get_info(), you can obtain information about the current context, so where you are issuing the command. get_info('channel') will return a string containing the name of the current channel, or if you are in a query window, the name of the person you are talking with.


That is it really, you now have seen the progression of a simple Perl script in XChat. There is much more you can do than just things like this script, but it works best with a fuller understanding of Perl, and then it just becomes a matter of becoming familiar with what you can do with the XChat Perl API.

Print - Recent Changes - Search
Page last modified on November 23, 2009, at 03:58 PM