# Chapter 5. Programming Your Sirc Client

Warning: to understand this you need to know perl (the programming language; read the perl man pages for more info), and have read the README thoroughly.

For a real usable example of a sirc script, look at the file `n0thing.pl`; if you wonder how you could do something in sirc script, try understanding the functions defined in there.

# 5.1. Commands

From `/loaded` scripts and `.sircrc.pl`, you can define new commands and give their implementation in perl.

These scripts are actually files of perl code, and they get loaded right into sirc's context.

To define a new command, all you need to do is define a sub with the name `cmd_yourcommandname` which does whatever you want it to do, and call `&addcmd("yourcommandname");`

You can also define some help for the command, by calling `&addhelp("yourcommandname", "First line of help\nSecond line of help...")`;

Your sub gets all of its arguments in the global variable `$args` (unparsed), its own name in `$cmd`, and the whole command line in `$line`.

It can also use a number of routines from the sirc client:

&load("file");

  loads a sirc script, searching in @loadpath. the ".pl" extension is optional.

&dosplat;

  turns a * into the current channel name, if it's the first word of `$args`

&getarg;

  to get the first word of `$args` in $newarg and the rest in `$args`

&yetonearg;

  same thing, removing a trailing : in `$args` if there's one

&eq("txt1", "txt2");

  tests case-insensitive equality

&sl("text");

  to send a line of text to the server (the trailing `"\n"` gets added automatically)

&tell("txt");

  sends text to the screen, adding a `"\n"`, and only if not in silent mode

&print("txt");

  sends text to the screen, adding a `"\n"`, regardless of silent mode

&getuserline("str", "prompt");

  prints "str" on the screen, puts "prompt" as a temporary prompt if using ssfe, and prompts the users for a line, returning it in `$_`

&getuserpass("str", "prompt");

  same for prompting passwords; ssfe will not echo the password

&dostatus;

  redisplays the status line

&msg("nck", "msg");

  sends a message, printing it. the destination can be a nick, a channel, or a =nick (DCC CHAT)

&notice("nck", "msg");

  sends a notice, printing it. the destination can be a channel or a nick

&say("msg");

  says something on the current channel, printing it

&describe("nck", "msg");

  sends a `/describe`, printing it

&me("msg");

  does an action on the current channel, printing it

&connect($fh, "host", port);

  opens a tcp connection with the given host and port. the first argument `($fh)` must be a variable and `&connect` sets it to the value of the file handle associated with the connection. `&tell's` a message and returns 0 if there's an error, otherwise returns 1.

&listen($fh, port);

  opens listening socket bound to the given port; lets the system pick a port if the specified port is 0 (or the second argument is not passed at all). the first argument must be a variable and `&listen` sets it to the value of the file handle associated with the listening socket. &tell's a message and returns 0 if there's an error, otherwise returns the port on which the socket listens.

&accept($nfh, $ofh);

  accepts a connection on the file handle `$ofh` (which must refer to a listening socket), and returns it in `$nfh`; `$nfh` must be a variable and will be changed by `&accept`. `$ofh` is automatically closed by `&accept`. returns a boolean value, but does not print an error message in any case.

&resolve("address");

  resolves a hostname into a packed in_addr (i.e a 4-byte string representing the IP address). the argument can be a hostname, an IP address written in dotted quad notation, or a (large) number representing the address, "read" as a 32-bit number in network order. if the resolution fails, returns a false result ("" or 0 or undef).

  to get a dotted quad from what `&resolve` returns, use `join(".", unpack("C4", &resolve("whatever")))`

&newfh;

  returns a fresh name for use as a filehandle

&doset("variable", "value");

  sets a value to a SET variable; the value is validated, and this has no effect if the value is incorrect or the variable does not exist. this is the only way scripts should ever change the values of SET variables, except possibly those that they define themselves.

&docommand("command");

  interprets a command line as if it were typed at the keyboard. a *single* leading "/" will disable alias/function expansion on the command.

  *warning* this calls the command dispatcher recursively from itself, which is pretty bad. there is a test against loops (a limit on recursion, set to 20), but it's mostly up to *you* to make sure your scripts work. perl being a language with strong and powerful control structures (unlike ircII...), recursion at this level should be avoided whenever possible.

You have access to the a number of global variables; note that some have been removed because they have been turned into SET variables, to be read in %set and written to with &doset.

Unless otherwise specified, these variables should be treated as read-only by scripts.

$version

  sirc's version - should always be a number, and never be modified by a user function

$add_ons

  additional modules loaded; scripts can add a "+scriptname" to it

$restrict

  set to true if sirc is running in restricted (secure) mode, which disallows access to the shell and to the filesystem

$maxrecursion

  number of times &docommand may be called recursively before giving a "max recursion exceeded" error (you can change this one, but it is not guaranteed to work on future versions where this might become a SET variable)

$nick

  your current nick

$server

  your current server

@channels

  list of channels you're on

$talkchannel

  your current channel (or '' if none)

%mode

  associative array with the modes of the different channels we're on. the channel names are all in lower case, and the mode is a string of letters without +'s or -'s, and without 'k' or 'l' either since those are treated separately. the value for channels without any mode is '', while the value for channels we're not on is undef.

%chankey

  keys to channels, undef if none or we're not on the channel. channel names are in lower case.

%limit

  limits to channels, undef if none or we're not on the channel. channel names in lower case.

%haveops

  associative array of booleans, true if we have ops on the channel. channel names are... you know how

$umode

  user mode, string of letters without +'s or -'s

$query

  whoever you're querying, or '' if no-one

%aliases

  associative array of defined substitution aliases; the alias name is in CAPS

%set

  associative array of SET variable values; the variable name is in CAPS too

%notify

  associative array of the notify list; the value for a given nick is 0 for "absent", or the time of the most recent notification for this nick

$bindaddr

  this is the IP address of the machine to which outgoing connections are bound, as far as sirc can tell. it changes when the "localhost" SET variable changes, and is set to the IP of the proxy machine when sirc is running with socks support loaded. `$bindaddr` is a 4-byte string representing a packed `in_addr;` you can get an integer out of it (as used by DCC CHAT/SEND) with `unpack("N", $binaddr);` and a dotted quad with `join(".", unpack("C4", $bindaddr));`

Unless otherwise specified, commands and hooks should never modify the parameters that are passed to them (i.e. do somethign like `$_[1]="some value"`. If they wish to modify local copies of them, they should start with `local(...)=@_;`

Also, if your script is going to use global variables, please make sure they're not likely to clash with sirc's own (same goes for file descriptor names, and procedures). A good convention would be to give all these variables and procedures a name that starts with the script name, or with a few letters from it. For example, in `n0thing.pl` all the script's global variables and internal procedures have names that start with "n_".

Example, which could be put into a file and `/load'ed` directly, of a command that will yeek on a channel if you specify one, and at a nick if you do too:

     sub cmd_yeek {
       &dosplat;		# if the 1st arg is *, replace it with $talkchannel
       &getarg;		# get 1st arg in $newarg
       local($channel)=($talkchannel);	# by default we talk to $talkchannel
       if ($newarg =˜ /^[\#\&]/) {		# if the 1st arg starts with # or &
         $channel=$newarg;			# talk there instead
         &getarg;				# and get an extra arg
       }
       if ($newarg) {	# look at whether we specified who we're yeeking at
         &describe($channel, "look at $newarg and *yeeeks*");
       } else {		# or not
         &describe($channel, "*yeeks* at the crowd");
       }
     }

     # \cb is the way to specify ^B in perl
     &addcmd("yeek");
     &addhelp("yeek", "Usage: \cbYEEK\cb [<channel>] [<nick>]
     Yeeks at the given nickname or at the whole channel.
     Examples: /yeek someone
               /yeek * someone
      	  /yeek #channel
      	  /yeek #channel someone");