(12Aug99, fotang@techie.com)

Hook Documentation for Sula PrimeriX II (draft 2)

This article documents hook types implemented in Sula PrimeriX (SPX).
A hook is triggered when a specified event happens. Please see the 'latest hook' section for new features that have not yet been documented.

Classification of hooks

Two classes of hooks are defined in Sula PrimeriX: static and preemptive hooks. There are two differences between them: Preemptive hooks enable the user to decide, in the moment that an event occurs, what to do with a message, and whether or not to let the client see the message.

How to define a hook

The ON command is used for setting quoted hooks. Type '/help on' for more.
To programmatically set a hook, the generic procedure gs-new-hook is available:
gs-new-hook message_type preemptive icase regex pattern [proc | #f]
gs-new-hook #message_type preemptive icase regex serial pattern [proc | #f]

This procedure is scary. However, you normally do not need to call it directly. You would use a wrapper function.

message_type is the kind of message for which we wish to set a hook. In other words, it is the hook type. message_type may also be a server numeric. Server numerics are 3 digit numbers originating from IRC servers. The generic hook type "RAW_IRC" may be used for all event types including server numerics. There are about 50 hook types currently defined. If the sharp sign '#' precedes message_type, the hook will be assigned the serial number serial.

preemptive, icase, and regex are each either true or false.
If preemptive is true, a preemptive hook will be set.
If icase is true, there will be no upper/lower case distinction when patterns are matched.
If regex is true, pattern will be considered to be a POSIX regular expression. If regex is false, pattern will be considered to be a shell (bash) pattern, and pattern matching will be done shell like.

serial is the serial number of the hook.

pattern is the expression against which we want to match messages. In other words, messages of type message_type will trigger this hook if the messages match pattern. If regex was false, we match against a shell expression. All shell metacharacters are then allowed in the pattern. Recall:
? matches any single character
* matches any sequence of letters (including blanks)
^ excludes a character or set of characters
[] a set of optional letters

If regex was true, we match against a modern regular expression "RE" [see regex(7)]. RE's are more powerful than shell expressions. Let's recall a few things about RE's. Let r be an extended regular expression. Then:
x matches the character 'x'
. matches any single charter
^r matches r, but only at the beginning of the message
r$ matches r, but only at the end of the message
r? matches 0 or 1 occurrence of r
r* matches any sequence of 0 or more matches
r+ matches a sequence of 1 or more r's
r{4} matches exactly 4 r's
r{4,} matches 4 or more r's
r{4,10} matches between 4 and 10 r's
[abT-W0-5] matches any character in the character class: an 'a', a 'b', any letter between 'T' and 'W', and any digit from 0 through 5.
[^abT-W0-5] matches any characters that is not in the class
r|s matches either r or the regular expression s
(r) matches r; parentheses are used to group RE's

A discussion of regular expressions is beyond the scope of this document. See regex(7) for more details.



Examples of extended regular expressions

"^([^ ]+ ){3}[^a-z0-9]{10,}$" matches a message whose first 3 words are followed by a portion that is at least 10 characters long. The portion contains neither lowercase characters nor digits.
E.g. "irc.eskimo.com,6667 xtr!meyo@another.net #test THIS IS-A-TEST!".

"^([^ ]+ ){3}[^a-zA-Z0-9]{4,}$" matches a message whose first 3 words are followed by a portion that is at least 4 characters long. The portion contains no alphabetic characters and no digits.
E.g. "irc.eskimo.com,6667 xtr!meyo@another.net #test !&%$)?( $,;%"

".*[!?]{5,}" matches a message with excessive !'s or/and ?'s or a combination of both (at least 5). E.g."irc.eskimo.com,6667 xtr!meyo@another.net #test anyone home!!??!!!??? Speak up."
In modern regular expressions, ^.[$()|*+?{\ are special characters. In shell expressions, ?*^[] are special characters. If a special character occurs in a pattern, the character has to be quoted, escaped, back slashed. (Note that special characters sometimes loose their special meaning, e.g. ^ inside [], and thus then need not be quoted.) Two backslashes are required. The following note is curled from the Guile Reference Manual.

Very important: Using backslash escapes in Guile source code (as in Emacs Lisp or C) can be tricky, because the backslash character has special meaning for the Guile reader. For example, if Guile encounters the character sequence `\n' in the middle of a string while processing Scheme code, it replaces those characters with a newline character. Similarly, the character sequence `\t' is replaced by a horizontal tab. Several of these escape sequences are processed by the Guile reader before your code is executed. Unrecognized escape sequences are ignored: if the characters `\*' appear in a string, they will be translated to the single character `*'.

This translation is obviously undesirable for regular expressions, since we want to be able to include backslashes in a string in order to escape regexp metacharacters. Therefore, to make sure that a backslash is preserved in a string in your Guile program, you must use two consecutive backslashes:

(define Info-menu-entry-pattern (make-regexp "^\\* [^:]*"))
The string in this example is preprocessed by the Guile reader before any code is executed. The resulting argument to make-regexp is the string `^\* [^:]*', which is what we really want.

This also means that in order to write a regular expression that matches a single backslash character, the regular expression string in the source code must include four backslashes. Each consecutive pair of backslashes gets translated by the Guile reader to a single backslash, and the resulting double-backslash is interpreted by the regexp engine as matching a single backslash character. Hence:

(define tex-variable-pattern (make-regexp "\\\\let\\\\=[A-Za-z]*"))

If proc is specified, then proc is any Scheme procedure that takes two arguments:

If proc is not given, or if FALSE is used, no action will be taken for matching events. For preemptive hooks this means that TRUE will be returned.

Testing hooks

You do not have to be on IRC in order to test your hooks. Set them on the hook type INPUT and simulate input by typing in the input field.

Wrapper functions for setting hooks

The procedures described in the following table call gs-new-hook with appropriate values.
wrapper function description
gs-shell-hook type pattern proc pattern is a shell expression; differentiate between
upper and lower case characters when matching.
gs-shell-hook #type serial pattern proc dito, but assign the serial number to the hook
gs-regex-hook type pattern proc pattern is a modern regex; case sensitive matching
gs-regex-hook #type serial pattern proc dito, but also use the serial number serial
gs-shell-preempt type pattern proc set a preemptive hook
gs-shell-preempt #type serial pattern proc dito, but assign serial number
gs-regex-preempt type pattern proc set a preemptive hook; pattern is a modern regexp
gs-regex-preempt #type serial pattern proc dito; assign the serial number serial
gs-ishell-hook type pattern proc same as the corresponding procedures above.
However, there will be no regard to case when messages are matched against the given patterns.
gs-ishell-hook #type serial pattern proc
gs-iregex-hook type pattern proc
gs-ishell-preempt type pattern proc
gs-ishell-preempt #type serial pattern proc
gs-iregex-preempt type pattern proc
gs-iregex-preempt #type serial pattern pro

gs-add-hook is equivalent to gs-ishell-hook.
gs-preempt is equivalent to gs-ishell-preempt.
gs-on is an obsolete pseudonym for gs-ishell-hook.

Serial numbers and the order of activation

An event is a unique type - pattern pair. An action is bound to it. However, it may sometimes be necessary to bind more than one action to an event. To this end, the event is assigned a so-called serial number. The procedures for assigning serial numbers are listed amongst the wrapper functions.

A serial number is a whole number between INT_MIN (-2147483648?) and INT_MAX (2147483647?) as defined in /usr/include/limits.h . Even when an event is not explicitly assigned a serial number, SPX silently assigns a serial number of zero to it.

The order in which hooks are triggered is determined by the values of their serial numbers: the priority of activation increases with decreasing serial number. Of non zero serial numbers, INT_MIN has the highest priority; INT_MAX, the least. Hooks having a serial number of zero are always triggered last. By assigning a zero serial number, you override the default action that the client normally takes for the event. That is, SPX will consider the event consumed as soon as it finishes working the hook.

Note also, that for messages from an IRC server, hooks on ``RAW_IRC'' are always checked as soon as a line is received from the IRC server. That happens before client parses the message, and a long time before before all other hook types are checked for matches. ``RAW_IRC'', especially when used with preemptive hooks, offers the user the possibility of seeing and possibly catching a message before the IRC client does.

Some simple examples for a start

1. Normally when one joins a channel, the IRC server sends numeric 333 informing the user of who set the channel topic and when they did it. Let's always ignore the message. Type the following and see what happens.

(gs-shell-hook 333 "*" #f); Topic for #44th was set by ir on Sun Feb 14...

2. Throw people out of the channel who use excessive CAPS and too many !'s and ?'s. Note the use of serial numbers. Without them the messages wouldn't appear on your screen.
(let()
  (define (kick_lamer lamer channel reason window)
    (if(channel:op? (gs-channel2 channel window)) ; are you channel op?
      (gs-execute window "kick " channel " " lamer " " reason)))
  (define (no-CAPS-please m w)
    (kick_lamer ($nick m) ($dest m) "Turn off the damn CAPS" w))
  ;; message or notice is longer than 8 chars but contains no lowercase characters and no digits:
  (gs-regex-hook "#public_msg" 001 "^([^ ]+ ){3}[^a-z0-9]{8,}$" no-CAPS-please)
  (gs-regex-hook "#public_notice" 001 "^([^ ]+ ){3}[^a-z0-9]{8,}$" no-CAPS-please)
  ;;EXcessive !!!! or ?????? or a combination of both (at least 5 together):
  (gs-regex-hook "#public_msg" 001 ".*[!?]{5,}" (lambda(m w)
    (kick_lamer ($nick m) ($dest m) "Calm down!" w))))

2b. Upon joining the channel #test on the Undernet, we do not want to see who created the channel.

(gs-add-hook 312 "*.undernet.org,* * * #test *"); #44th was created on Sun Feb. 14...
3. Let's ignore all flooders from aol.com for 40 minutes. If were to ignore manually, we would have to type something like "/ignore *!*user@host.domain msg for 40 minutes''. We shall use the procedure gs-exec to execute the same command from within the Scheme interpreter.
(gs-add-hook "#FLOOD_PRIVATE" 100 "* *aol.com" (lambda(m w)
   (gs-execute w "ignore *!*" ($userhost m) " for 40 minutes"))))


4. Let's do something silly: prevent the user from signing off port 6665 of any Undernet servers. Just ring the bell and stay put:

(gs-preempt "signoff" "*.undernet.org,6665 *" (lambda(m w)
    (gs-echo "Sorry, cannot sign off 'tis server!" w)
    (gs-bell) #t))


5. If we are ignoring private messages from somebody and they ask a question in a channel, we ban them if we're chan op. Also ignore the question. Recall that channel names begin with either '&' or '#'.

(gs-preempt "RAW_IRC" "* * PRIVMSG [&#]* *\\?*" (lambda(m w)
  (define from (next-word m 1))
  (if(gs-ignored? from 'msg) ;;if they match any msg ignore patterns
     (let
      ((channel (next-word m 3))
       (sender ($userhost m)))
      (if(channel:op? (gs-channel2 channel w)) ;; if we are we chan op
        (gs-execute w "mode " channel " +b *!*" sender))
      #t))));; return true to cancel the message

Removing hooks

A graphical interface (the event tool) is available for viewing and removing hooks. Click on the appropriate icon on the control window. The procedures for programmatically deleting hooks are described below.

Removing static hooks

(gs-delete-hook [#]<type> [<numeric>] [<pattern>])

Gs-delete-hook directly calls the quoted ON command with its arguments. Type '/help on' for details. The ON command is used as follows to delete a hook:
command effect
ON -del type removes every hook of the specified type that does not have a serial number.
ON -del #type removes all hooks of the given type if they have a non zero serial number.
ON -del type pattern removes all hooks of type type for which pattern is exactly the specified pattern. If icase is true for a hook, strcasecmp(3) is called to compare the patterns. Otherwise, strcmp(3) is used.
ON -del #type ser_no removes all hooks of type type which have the given serial number.
ON -del #type ser_no pattern removes all hooks having the given serial number and whose patterns are exactly the same as the given pattern. If icase is true for a hook, strcasecmp(3) is called to compare the patterns. Otherwise, strcmp(3) is used.

If we elect not to use gs-delete-hook, then we have to pass the ON command to the procedure gs-exec:

(gs-exec "on -del <arguments as in the table above>")

The arguments to ON -del are exactly the same as those that were passed to gs-XXX-hook.
As an example, let's suppose we no longer wish to ignore those from aol.com who flood us. We many issue any of the following:

(gs-delete-hook "#flood_private" 100); removes all hooks on flood_private which have the serial number 100.

(gs-exec "on -del #flood_private 100 * *aol.com"); same as above but only if pattern is "* *aol.com *".

As a further example, if we decided to start seeing who set channel topic, the following would do:

(gs-exec "on -del 333")

Removing preemptive hooks

The procedure gs-remove-preempt is used for removing preemptive hooks. gs-delete-preempt has the same syntax as gs-delete-hook. Example:

(gs-remove-preempt "RAW_IRC * * PRIVMSG [&#]* *\\?")
(gs-delete-preempt "#flood_private" 100)

Hook types

There are about 60 different hook types. This count does not include hooks that may be set on server numerics.
Each hook type refers to a specific message type. One may divide message types into two main groups:
  1. messages from an IRC server. These are called server messages.
  2. miscellaneous messages. These originate from DCC connections, named connections, or as a result of some user action. These are client or internal messages.

Server messages

The structure of server messages is always the same:

      servername,portmessage

servername is the name of the server to which you are connected.
port is the port of the connection to the server.
Thus it is possible to restrict matches to particular servers!
message is the message part of the line which was received from the IRC server. If a hook is set on a server numeric or on the hook type RAW_IRC, message is raw IRC protocol, unchanged from how we received it. Otherwise, the value of message depends upon the hook type.
To illustrate this, let's say we got a message from channel #lameric on the IRC server irc.primenet.com, port 6667. Let the sender of the message be wale!wm@f2.haha.com and the text of message, "I think I am a little high!". A hook of type "PUBLIC_MSG" would then receive the following message:

irc.primenet.com,6667 wale!wm@f2.haha.com #lameric I think I am a little high!

It is up to the action bound to the hook to extract whatever it needs from the above message. In particular, if m is a private message/channel message, then without using any of the elaborate Guile string functions, the message can be split as follows:

  server   := (substring m 0 (string-index m #\,))
  sender   := (next-word m 1)
  nickname := (sender2nick (next-word 1))
  channel  := (nex-word m 2)
  msg      := (string-rest m 3)
To see raw IRC server messages, /SET show_raw_msg on.
Using the untested convenience procedures
$server,
$port,
$nick,
$userhost,
$dest and
$msg,
specific components of a server message can be extracted. ($server m) returns the server name, ($nick m) returns the nickname of the person who caused the message, ($userhost m) returns their user@host.domain, ($dest m) returns the destination of the message (channel or nickname), and ($msg m) is the text of the message.

Example: Attempt to echo back all notices from nick BlowFish on channel #lamerz. The IRC protocol does not allow this so SPX probably won't do it.

(gs-on "#public_notice" -100 "* blowfish!* #lamerz *" (lambda (m w)
  (gs-execute w "notice " ($nick m) " " ($msg m))))
With the exception of RAW_IRC and numeric replies, the words in message generally have the following meanings:
$0 server,port (e.g. "irc.eskimo.com,6667")
$1 sender of the message (nick!username@host.domain). Any tilde '~' in the user name has already been removed.
$2 destination of the message (channel or nickname)
Documentor's note:
We use $0, $1,..., $n to respectively denote the first, second, and the (n-1)th words of a string.
We use $0-, $1-, ..., $(n-1) to denote the rest of the string starting from, and including, the first, the second , and the nth word.
Basically,
    $1    := (next-word str 1)
    $1- := (string-rest str 1)
etc.

Depending on the message type, additional components may be available as listed in the following table. Hooks not listed in the table take the raw, unparsed server message.
Hook type Pos Position description
JOIN
PART
$1
$2
person who joined/left the channel
channel name
NICK $1
$2
person who changed nickname (nick!user@host)
new nickname
QUIT $2- Sign off message
ACTION
PRIVATE_MSG
PUBLIC_MSG
PRIVATE_NOTICE
PUBLIC_NOTICE
SERVER_NOTICE
$3- message text
CTCP_REPLY $3- <command name> <text of the reply>
INVITE $3 channel
TOPIC $2
$3-
channel
new topic
MODE $2
$3-
nickname or channel of mode change
the modes
KICK $1
$2
$3
$4-
kicker!user@host
person kicked
channel
reason
CTCP_SED $3- the undecoded message (PRIVMSG is later called after decoding)
CTCP_ALL
CTCP_UNKNOWN
$3- <Command name> <arguments>
E.g. (gs-add-hook "CPCP_ALL" "* * * SOUND *") ...
=>Commnand name=SOUND, arguments=all else after "SOUND"
CTCP_HELP
CTCP_PING
$1
$2
$3-
sender
destination
arguments
CTCP_FINGER
CTCP_VERSION
CTCP_USERINFO
CTCP_TIME
CTCP_SOURCE
CTCP_CLIENTINFO
$3- nothing
FLOOD_PRIVATE $1
$2
$3-
flooder (nick!username@host)
your nickname
nothing
FLOOD_PUBLIC $1
$2
the flooder
channel
DCC_SEND_REQUEST $3- assumption: filename inaddr port cksum ...
DCC_CHAT_REQUEST $3
$4
inaddr
port
If a hook type is not present in the above table, then the hook type is passed a raw, unparsed server message.


People not running identd(8) usually have a tilde '~' before their names. Sula PrimeriX removes the tilde before checking hooks. It is therefore not possible for you to restrict matches to people not using identd, unless you use the hook type RAW_IRC. For example, in order to refuse private questions from people who aren't running identd, the following hook would fail:

  (gs-shell-hook "private_msg" "* *!~* * *\\?*" (lambda(m w)
    (define the-command (string-append
                "/notice " ($nick m) " Run identd first!"))
    (gs-exec the-command w)))

However, using RAW_IRC works:

   (gs-shell-hook "raw_irc" "* *!~* PRIVMSG [^&#]* *\\?*" (lambda(m w)
   (define the-command
     (string-append "/notice " ($nick m) " Run identd first!"))
   (gs-exec the-command w)))

The pattern "*!~*" matches nick!~name@host.domain (the sender of the message). Channel names start with either "#" or "&", hence the pattern [^&#]* is used to catch questions that aren't destined for a channel.


Server message types

The following table contains a list of all hook types that may be set on server messages.
Hook type Trigger
ACTION CTCP action (public and private), usually as a result of somebody doing /me blabla.
CTCP_ALL all CTCP commands received.
The following CTCP hook types are optionally compiled into the client
CTCP_CLIENTINFO
CTCP_FINGER
CTCP_HELP
CTCP_PING
CTCP_SED
CTCP_SOURCE
CTCP_TIME
CTCP_USERINFO
CTCP_VERSION
CTCP_ALL "* CLIENTINFO "
CTCP_ALL "* FINGER "
CTCP_ALL "* HELP *"
CTCP_ALL "* PING"
CTCP_ALL "* SED  *"
CTCP_ALL "* SOURCE"
CTCP_ALL "* TIME"
CTCP_ALL "* USERINFO"
CTCP_ALL "* VERSION"
CTCP_REPLY received a CTCP reply.
CTC_UNKNOWN received an unknown CTCP request
DCC_CHAT_REQUEST received a request to chat
DCC_SEND_REQUEST received a request to receive a file
ERROR IRC server sent an error text
INVITE got an invitation to join a channel
JOIN somebody joined a channel
JUNK IRC server sent junk (unknown stuff)
KICK somebody got kicked off a channel
MODE mode change (channel or person)
NICK somebody changed nick
NOTE received a note
PART somebody left channel
PING received an IRC server ping
PONG received an IRC server pong
FLOOD_PRIVATE current message triggered private flood alarm
PRIVATE_MSG received a private message
PRIVATE_NOTICE received a private notice
FLOOD_PUBLIC this message triggered channel flood alarm
PUBLIC_MSG got a channel message
PUBLIC_NOTICE got a channel notice
QUIT someone signed off IRC
RAW_IRC got a line from an IRC server.
SILENCE Undernet servers send silence messages (can't figure that out)
TOPIC channel topic changed
SERVER_NOTICE got a server notice.
WALLOPS public address system; abused to extinction
N Server numeric reply. N is a number, usually between 1 and 999 inclusive. For a list of some known numeric replies, see the file include/server-numerics.h.

Further Examples

1. Rejoin a channel 20 seconds after you've been kicked. Auto rejoin must be set off for this to work. You may also prefer to use a lower serial number.

(gs-set! 'autorejoin #f) (gs-on "#kick" 5000 "*" (lambda(m w)
  (define server (gs-window-server2 w)); obtain the server object for the connection
  (define kicked-person (next-word m 2))
  (define our-nick (server:nick server))
  (if(string-ci=? kicked-person our-nick);; if 'you' have been kicked
    (begin (define channel (next-word m 3))
      (define chankey (channel:key (gs-channel2 channel w))) ;; save channel key
      (gs-new-alarm 20 (lambda(junk morejunk) ;; set a timer for 20 seconds
        (gs-exec (string-append "JOIN " channel " " (if(not chankey) ""
        chankey)) w)))))))

2. Ignore CTCP error messages from clients that do not understand encrypted messages SED.

(gs-preempt "CTCP_REPLY" "*ERRMSG *SED*")

3. Ignore all channel messages on Dalnet if you're marked away.

(gs-preempt "RAW_IRC" "*.dal.net,* * PRIVMSG [&#]* *" (lambda(m w)
   (server:away? (gs-window-server2 w))))

4.Auto-op everyone from the .de TLD who join any channel on which you are channel op.

(gs-on "#join" 101 "* *.de *" (lambda(m w)
  (define joined_channel (next-word m 2))
  ;; now op them if u are op
  (if(channel:op? (gs-channel joined_channel w))
    (gs-execute w "/mode " joined_channel " +o " ($nick m)))))

Client messages

The structure of a client message depends on the particular hook type. Some DCC_XXX_XXX hooks are subject to change and may even be completely removal.
Hook type Activator
DCC_CHAT_DONE After closing a DCC chat connection. 
Message := , nick wrote read remote email_addr
nick: nickname of person with whom chat took place
wrote: bytes written during the chat
read: bytes read
remote: remote host
email_addr: user@host.domain of nick
DCC_CHAT_LOST Lost a DCC chat connection.
Message is the same as above in DCC_CHAT_DONE
DCC_CHAT_MSG Read a chat message.
Message := , nick msg
nick: nickname to which connection was created
msg: line read
DCC_CHAT_START DCC chat connection established.
Message := , nick remote email_addr
nick: nickname to which connection has been established
remote: remote IP
email_addr: user@host.domain
DCC_GET_DONE Finished receiving a file.
Message := , remote email_addr filename size read
remote: remote host (still unclear)
email_addr: sender's user@host.domain
filename: the name of the file
size: file size as we were told
read: how many bytes we received
nickname appears to be missing...
DCC_GET_REQUEST (unimplemented)
DCC_GET_START About to start receiving a file.
Message := , nick email_addr filename size fd
nick: person sending the file
email_addr:their user@host.domain
filename: name of the file
size:file size
fd: socket descriptor
DCC_SEND_DONE finished sending a file.
Message := , remote filename size sent
remote: remote host/user@host.domain
filename: the name of the file
size: file size
read: how many bytes we sent
nickname appears to be missing...
DCC_SEND_START About to start sending a file.
Message := , nick filename filesize remote_host email_addr
EXIT There was a request to shutdown the client. Recursive exits won't work. Use /exit -f to skip all EXIT hooks. One may thus really shutdown client from within this hook type.
Message:="" (none).
INPUT User typed something. Activated on each line of user input.
Message := input
NC Parsed one line from a named connection. 
Output from an NC is broken into lines. This hook type is checked on each line. If the line contains a control command then this hook type is not triggered. See Progamming-2 for details.
Message := name input
name: the name of the connection.
input: a line of input.
NC_LOST A named connection has been closed.
Message := name
name: the name of the connection that went away
NC_RAW Read from a named connection. The output is unparsed, unchanged from the way it was read. NC_RAW can be more effective than NC because you may gain from the fact the other end of the connection buffered its output. If one created a connection to "finger fooUser", bash normally buffers the result before sending. NC hooks, however, break the result into lines. NC_RAW does not.
Message := name input
name: the name of the connection.
input: the raw text read.
NOTIFY_SIGNOFF Somebody on an active notify group signed off.
Message := server,port nick
server: the server name
port: the port of your connection
nick: person who signed off
NOTIFY_SIGNON Somebody on an active notify group signed on.
Message := server,port nick
server: the IRC server name
port: the port of your connection
nick: person who signed on
SEND_ACTION After sending an 'action' text.
Message := server,port destination msg
server: name of IRC server
destination: nick or channel name
msg: the text of the action
SEND_CHATMSG After writing to a DCC chat connection.
Message := , nick email_addr msg
nick: nickname to which connection was created
email_addr: their user@host.domain
msg: text to be sent
SEND_DCC_CHAT (unimplemented)
SEND_ENCRYPTED
(this is unclear;
currently suspended)
User wants to send an encrypted text.
Message := server,port destination msg
server: name of IRC server
destination: nick or channel name
msg: the plain text
SEND_PRIVATE_MSG After sending a private message.
Message as above in SEND_ENCRYPTED.
SEND_PRIVATE_NOTICE After sending a private notice.
Message as above in SEND_PRIVMSG.
SEND_PUBLIC_MSG After sending a message to a channel.
Message as above in SEND_PRIVMSG.
SEND_PUBLIC_NOTICE After sending a notice to a channel.
Message as above in SEND_PRIVMSG.
SEND_TO_SERVER About to finally send a line to an IRC server. All messages to the server pass through this hook type so don't mess with it. This hook may be circumvented by sending directly to the server using gs-raw-write.
Message := server,port msg
server: server name.
msg: the text.
SERVER_FAILED An attempt to connect to an IRC server failed. This usually follows a /server command. Connection refused or couldn't resolve host name or some network fsck$!% or already connected to the server or ...
Message := server,port nick
server: server name
port: annual income tax
nick: the nick we would have connected with.
SERVER_LOST Lost the connection to an IRC server.
Message := server,port nick server
server: server name (alias)
port: Mathew 7:7 (Ask and you will be ...)
nick: the nickname that we were using
server: server's real name
SIGNOFF User wishes to sign off an IRC server. Use /quit -f to override SIGNOFF hooks. Recursive signoffs from the same server would fail.
Message := server,port nick
server: server name
port: annual income tax
nick: current nickname on the server.
UPDATE_STATUS The status bar of a channel window is about to be redrawn. If an attempt is made to update the status bar from within this hook type, the attempt would fail.
Message := ""
UPDATE_STATUS_DONE The status bar of a channel window has just been redrawn.
Message:= the new status text
The status text is retrieved using the procedure gs-get-statusline. Example of a status text: xtr@  (+i) on #44th (+nt)
WINDOW_CREATE A new channel window has just been created and displayed.
Message := ""
WINDOW_DESTROY User wants to destroy a channel window.
Message := ""

More examples

1. Before client shutdown, send QUIT command to all connected IRC servers. A very large serial number is used so that the hook is called as late as possible.
(define quit-message "See y'all in hell...\n")
(gs-on "#EXIT" 999999 "*" (lambda(junk mooo)
 (define socket-list (servers-socket-fd)); get a list of all socket descriptors
 (if(not(null? socket-list)); at least 1 server connection exists
    (for-each
        (lambda(i)
           (gs-raw-write (car i) (string-append "QUIT :" quit-message "\n")))
        socket-list))))
2. Don't send anything to the server that contains the word "animalistic" (case insensitive) anywhere in it. The first word is the name and port of the server so we skip it.
(gs-iregex-hook "send_to_server" "^[^ ].* animalistic")
*There*'s a problem with the last example. I cant work it out.
--watch this space for more--

Latest hook changes

- Sula Primerix II - 0.09.2b (1Aug99)
-------------------------------------
o New hook types
    1. RAW_NUMERIC
    2. RAW_NON_NUMERIC
        These are cheaper alternatives to RAW_IRC. RAW_IRC ::= RAW_NUMERIC +
        RAW_NON_NUMERIC.
    3. IRC_LINE_OUT
        IRC_LINE_OUT is triggered when a line from IRC is about to be displayed on the screen.
        The line has already been processed (hook-checked, dissected, formatted, fired at, etc), and the user is about to see
        the final result. This hook type offers the possibility of
        setting hooks based on what the user *actually* sees from IRC.
        Example: How to grab HT URLs
        /on #irc_line_out 200 *http://?*.?* (grab-url "$2-")
        or, since we're only interested in incoming lines,
        (gs-add-hook "#irc_line_out" 200 "*http://?*.?*" (lambda(m w)
          (if(not(string=? "OUTGOING" (next-word m 1))) (grab-url m))))
        'grab-url' would be a procedure that can extract URLs from a string.
        
        Arguments to IRC_LINE_OUT: server,port str_type str_line int_flag
        
        As usual, server,port is the server to which you're connected.
        type describes the kind of message we're dealing with. It may take any
        of following values:
         "PRIV_MSG", "PUB_MSG", "PRIV_NOTICE", "PUB_NOTICE",
        "ACTION",  "DCC", "CTCP_REPLY", "NOTE","SNOTICE", "QUIT", "TOPIC",
         "OUTGOING", "KICK", "WALLOP", "NUMERIC","MISC".
        "OUTGOING" is for messages that you are sending out. "MISC" is for all else not mentioned.
        str_line is the final text to be displayed; if flag==0, text is to be displayed in the upper window.
        
        Much information is hidden by the time a message
        reaches IRC_LINE_OUT (no sender name or address, no destination etc).
        This is because str_line may not be what somebody actually sent; it
        might have been altered along the way by other hooks or by a format
        string. For example, if the variable fmt_pub_msg got set to "<%n> crap:
        %p", channel messages would get the word "crap" added to them and it would be
        inaccurate to think that the author of message sent that.

o changes to hook type DCC_SEND_START:
    one argument has been added: the position (offset) in the file from
    where sending is starting.

- Sula Primerix II - 0.09.3 (6Aug99)
------------------------------------
  Normally, hook handlers get only a copy of the server or client message to
  work with. All modifications to the message are local to the hook.
  It is now possible for two hook types to globally *modify* the IRC or client
  message such that subsequent hooks and the user never see the original text.
  
  - Hook type RAW_NON_NUMERIC
    Is allowed to modify messages by calling the procedure
    gs-modify-irc-output. Other hooks and the client then never see the
    original IRC message and cant tell whether it's been modified or not.
    For fear of abuse, support for this tampering must be built into the
    client using -DMUTABLE_IRC at compile time. Also, at runtime, 'mutable_irc'
    must be SET on. And since RAW_NON_NUMERIC doesnt work on server numerics,
    numeric replies cannot be modified.

    Hooks of type RAW_NON_NUMERIC always use the original message.

    See the scripts scripts/afuck.scm for an example.
  
  - Hook type INPUT
    Can modify user input by calling the procedure gs-modify-user-input
    with the replacement text as argument.

  Related flags (currenntly for RAW_NON_NUMEIRIC)
  + runtime: /set mutable_irc on|off|toggle
  + X resource entry: *.mutable_irc: on|of
  + command line option: -mutable_irc    (implies true)
EOF