;;;
;;; ZenIRC Internet Relay Chat client.
;;; Ben Mesander         <ben@piglet.cr.usgs.gov>
;;; Noah Friedman        <friedman@prep.ai.mit.edu>
;;; Eric Prestemon       <eric@american.edu>
;;; Charles Hannum       <mycroft@ai.mit.edu>
;;; Richard Todd         <rmtodd@servalan.servalan.com>
;;;
;;; $Header: /home/fsf/friedman/src/zenirc/zenirc/RCS/zenirc.el,v 1.72 1993/11/28 18:25:29 ben Exp $
;;;

;;;###autoload
(defvar zenirc-buffer-name "*zenirc*"
  "*Basic buffer name for Zen Internet Relay Chat.")

(defvar zenirc-IRCSERVER-alist
  (let ((str (getenv "IRCSERVER"))
        (match-data (match-data))
        result
        tmplist
        tmp)
    (while (and str (not (string= str "")))
      (if (string-match "[ \C-i\C-j\C-m]+" str)
          (setq tmp (substring str 0 (match-beginning 0))
                str (substring str (match-end 0)))
        (setq tmp str
              str nil))
      (while (and tmp (not (string= tmp "")))
        (if (string-match ":\\([^:]*\\)$" tmp)
            (setq tmplist (cons (substring tmp 
                                           (match-beginning 1)
                                           (match-end 1))
                                tmplist)
                  tmp (substring tmp 0 (match-beginning 0)))
          (setq tmplist (cons tmp tmplist)
                tmp nil)))
      (and tmplist
           (let* ((portcdr (cdr tmplist))
                  (port (car portcdr)))
             (and port
                  (setcar portcdr (string-to-int port)))
             (setq result (cons tmplist result)
                   tmplist nil))))
    (store-match-data (match-data))
    (nreverse result))
  "*Association list of port/password/nick info for each server.

Zenirc will attempt to connect to each `host' at `port' in turn until a
successful connection is made, using the username `user' (and `password' if
the server requests one).  Your nickname is set to the value `nick'.

The default value of this alist is determined by the value of the
environment variable `IRCSERVER', which should be in the format
`host:port:password:nickname:username'.  If more than one host:etc group is
desired, separate each group with any nonzero amount of whitespace composed
of spaces, tabs, and/or newlines.

The actual data types of the atoms of each association are string (server),
int (port), string (password), string (nickname), string (username).")

;;;###autoload
(defvar zenirc-server 
  (cond
   (zenirc-IRCSERVER-alist
    (car (car zenirc-IRCSERVER-alist)))
   ("irc-2.mit.edu"))
  "*The hostname of the IRC server to which to connect.

This is initialized to the first server in `zenirc-IRCSERVER-alist' if that
variable is non-`nil', or \"irc-2.mit.edu\" by default.")

(defvar zenirc-servername nil
  "*The server name of the IRC server current connected to")

;;;###autoload
(defvar zenirc-port
  (cond
   ;; This `and' is necessary because we want the TEST to be nil if either
   ;; condition is false, not set the defvar to nil if only the latter is nil.
   ((and zenirc-IRCSERVER-alist
         ;; This is safe; if assoc returns nil, nth will always return nil.
         (nth 1 (assoc zenirc-server zenirc-IRCSERVER-alist))))
   (6667))
  "*The TCP port associated with the irc server specified by `zenirc-server'. 

If the server initially specified in `zenirc-server' appears in
`zenirc-IRCSERVER-alist', and a port is explicitly associated with that
server, `zenirc-port' is initialized to that port.  Otherwise,
`zenirc-port' defaults to 6667.")

(defvar zenirc-user-login-name
  (cond
   ;; This `and' is necessary because we want the TEST to be nil if either
   ;; condition is false, not set the defvar to nil if only the latter is nil.
   ((and zenirc-IRCSERVER-alist
         ;; This is safe; if assoc returns nil, nth will always return nil.
         (nth 4 (assoc zenirc-server zenirc-IRCSERVER-alist))))
   ((user-login-name)))
  "*The username with which you signon IRC.

This is usually your username on the system as returned by the function
`user-login-name'.  However, if the server initially specified in
`zenirc-server' appears in `zenirc-IRCSERVER-alist' and a username is
explicitly associated with that server, `zenirc-user-login-name' is
initialized to that value.")

;;;###autoload
(defvar zenirc-nick
  (cond
   ;; This `and' is necessary because we want the TEST to be nil if either
   ;; condition is false, not set the defvar to nil if only the latter is nil.
   ((and zenirc-IRCSERVER-alist
         ;; This is safe; if assoc returns nil, nth will always return nil.
         (nth 3 (assoc zenirc-server zenirc-IRCSERVER-alist))))
   ((getenv "IRCNICK"))
   (zenirc-user-login-name))
"If the server initially specified in `zenirc-server' appears in
`zenirc-IRCSERVER-alist' and a nickname is explicitly associated with that
server, `zenirc-nick' is initialized to that nickname.  If this is not the
case but the environment variable `IRCNICK' is set, `zenirc-nick' is
initialized from that.  As a last resort, it defaults to the value of the
variable `zenirc-user-login-name'.")

;;;###autoload
(defvar zenirc-name (or (getenv "IRCNAME") (user-full-name))
  "*The name which you use on IRC.
The default is your GECOS information.")

;;;###autoload
(defvar zenirc-password nil "*Connection password for your IRC server.
The default is none.")

(defvar zenirc-userinfo "Oink."
  "*Reply to USERINFO ctcp")

;;;###autoload
(defvar zenirc-mode-map '()
  "Sparse keymap for zenirc-mode")
(cond ((not zenirc-mode-map)
       (setq zenirc-mode-map (make-sparse-keymap))
       (define-key zenirc-mode-map "\n" 'zenirc-send-line)
       (define-key zenirc-mode-map "\C-m" 'zenirc-send-line)))

(defvar zenirc-ignorance-list 
  '("craig@netsys1.netsys.com" "fnord") 
  "*Annoying things that should be ignored")

(defvar zenirc-signal-list '() 
  "*Things that should cause signal notification to take place")

(defvar zenirc-beep-on-signal nil "*Should a signal make emacs beep?")

(defvar zenirc-send-ctcp-errmsg-on-unknown t
  "*Should ZenIRC reply to unknown CTCP queries with an ERRMSG reply?")

(defvar zenirc-send-ctcp-errmsg-on-unbalanced t
  "*Should ZenIRC reply with an ERRMSG to people who send an unbalanced CTCP query?")

(defvar zenirc-verbose-ctcp t 
  "*Should ZenIRC tell you when you send CTCP replies to people?")

(defvar zenirc-fingerdata (concat (user-full-name) " ("(user-real-login-name)
				  "@" (system-name) ")" )
  "*CTCP FINGER reply data.")

(defvar zenirc-source "brrcrftp.cr.usgs.gov:/pub/ben:zenirc.tar.gz"
  "Where to get ZenIRC from")

					; while developing, this defvar is a big hassle - make it a setq
					;(defvar zenirc-text-list 
(setq zenirc-text-list
      '((join_you . "[info] Joining channel: %s\n") ; your channel join message
	(join . "[info] %s has joined %s\n") ; channel join message
	(s001 . "[info] You are wasting time.\n") ; welcome to irc message
	(s002 .				; server name & version # msg
	      "[info] Your IRC server is %s running ircd version %s\n")
	(s003 . "[info] This server was created %s\n") ; when the server was built
	(s251 .				; # users on connect message
	      "[info] There are %s visible and %s invisible users on %s servers.\n")
	(s252 . "[info] There are %s major dweebs online.\n") ; irc operators msg
	(s253 . "[info] There are %s unknown connections.\n") ; unk connects msg
	(s254 . "[info] There are %s channels\n") ; number of channels
	(s255 .				; # of clients and servers
	      "[info] There are %s clients and %s servers connected to this server\n")
	(s301 . "[info] %s is away: %s\n") ; someone is away
	(s302 . "[info] userhost: %s\n") ; userhost reply
	(s305 . "[info] You are no longer away\n")
	(s306 . "[info] You are away\n")
	(s311 . "[info] %s (%s@%s) is %s\n") ; user part of /whois list
	(s312 . "[info] %s iswas using server %s (%s)\n") ; server part of /whois list
	(s313 . "[info] %s is a major dweeb.\n") ; /whois operator status
	(s314 . "[info] %s (%s@%s) was %s\n") ; user part of /whowas list
	(s315 . "[info] End of /who.\n") ; end of /who list replies
	(s318 . "[info] End of /whois.\n") ; end of /whois list replies
	(s317 . "[info] %s has been idle %d seconds\n") ; /whois idle time
	(s319 . "[info] %s is on: %s\n") ; channel part of whois data
	(s321 . "[info] Channel\tUsers\tTopic\n") ; header for LIST cmd
	(s322 . "[info] %s\t%s\t%s\n")	; each channel in LIST cmd
	(s323 . "[info] End of /list.\n") ; trailer for LIST cmd 
	(s324 . "[info] Mode for %s is %s %s\n") ; channel mode
	(s331 . "[info] %s has no topic\n") ; no topic message
	(s332 . "[info] %s topic: %s\n") ; topic message
	(s341 . "[info] You are inviting %s to %s\n") ; invite reply
	(s342 . "[info] You are asking %s to waste time\n") ; summon reply
	(s351 . "[info] Version: %s %s %s\n") ; version reply
	(s352_header . "[info] Nickname  Stat Name of Channel User@host (Hop count  Name)\n") ; header for /who list reply
	(s352 . "[info] %-9s %-3s  %-15s %s@%s (%s)\n") ; /who list reply
	(s353 . "[info] Users on %s: %s\n") ; displayed after channel join
	(s364 . "[info] %s %s %s\n")    ; /links reply
	(s365 . "[info] end of /links\n") ; end of /links reply
	(s367 . "[info] %s ban %s\n")     ; banlist reply
	(s368 . "[info] end of banlist\n") ; end of banlist reply
	(s371 . "[info] %s\n")          ; info reply
	(s372 . "[motd] %s\n")		; message of the day
	(s375 . "[motd] Message Of The Day:\n")	; start of motd
	(s376 . "[motd] End of motd\n") ; displayed at end of motd
	(s381 . "[info] You are now a major dweeb\n") ; irc op status
	(s382 . "[info] Rehashing: %s\n")            ; rehash server msg
	(s391 . "[info] Time for server %s: %s\n")   ; TIME reply
	(s392 . "[info] Userid   Terminal  Host\n")  ; header for users rpl
	(s393 . "[info] %s\n")                       ; body of users rpl
	(s395 . "[info] Nobody logged on\n")         ; nobody for users rpl
	(s401 . "[info] No such nick/channel: %s\n") ; there is no such nick/chan
	(s402 . "[info] No such server: %s\n") ; there is no such server
	(s403 . "[info] No such channel: %s\n")	; there is no such channel
	(s404 . "[info] You cannot send to %s.\n") ; you can't send to channel
	(s405 . "[info] Too many channels: %s\n") ; too many channels
	(s406 . "[info] Server has no record of nickname: %s\n") ; no whowas data
	(s407 . "[info] Duplicate recipients. No message sent: %s\n") ; user@host
	(s411 . "[info] No recipient given.\n")	; no recipient given
	(s412 . "[info] No text to send.\n") ; you didn't send anything.
	(s413 . "[info] No toplevel domain: %s\n") ; no toplevel domain spec
	(s414 . "[info] Wildcard in toplevel domain: %s\n") ; wild toplevel
	(s421 . "[info] This looks like spam to me: %s\n") ; you sent server spam
	(s422 .				; deprecate operators
	      "[info] Some major dweeb doesn't know enough to have a motd file\n")
	(s423 .				; deprecate operators
	      "[info] Some major dweeb at %s is too ignorant to provide admin info\n")
	(s431 . "[info] No nickname given\n") ; you didn't provide a nick
	(s432 . "[info] Invalid nickname: %s\n") ; invalid nick
	(s433 . "[info] Nickname already in use: %s\n") ; invalid nick
	(s436 . "[info] Nick collision kill: %s\n") ; nickicide
	(s441 . "[info] %s is not on %s\n") ; can't do it to those not present
	(s442 . "[info] You are not on %s.\n") ; you can't do that dave.
	(s443 . "[info] %s is already on channel %s.\n") ; invite error
	(s444 . "[info] %s is not logged in\n")	; SUMMON reply
	(s445 . 
	      "[info] Some major dweeb won't let you do summon\n") ; disabled summon
	(s446 . "[info] Some major dweeb won't let you do users\n") ; disabld users
	(s451 . "[info] You have not registered\n") ; gotta do the USER NICK thing
	(s461 . "[info] Not enough parameters: %s\n") ; as 421
	(s462 . "[info] You may not reregister\n") ; cannot USER twice
	(s463 .				; server refuses this client
	      "[info] Some fascist major dweeb will not let you connect\n")
	(s464 . "[info] Password is incorrect\n") ; bad PASS command
	(s465 . "[info] You are not allowed to use this server.\n") ; creep
	(s467 . "[info] Key for %s is already set.\n") ; chan key set already
	(s471 . "[info] Cannot join %s (user limit reached).\n") ; too many ppl
	(s472 . "[info] %s is an unknown mode character.\n") ; duh
	(s473 . "[info] Cannot join %s (invite only).\n") ; fascist nerds
	(s474 . "[info] Cannot join %s (ban).\n") ; you're banned
	(s475 . "[info] Cannot join %s (channel key).\n") ; bad key
	(s481 . "[info] You are not a big enough dweeb to do that.\n") ; oper only
	(s482 . "[info] You are not a powermonger for %s.\n") ; chanop needed
	(s483 . "[info] Duh. You cannot kill a server\n") ; can't kill a server
	(s491 . "[info] No major dweebs allowed from your host\n") ; no o-line
	(s501 . "[info] Unknown user mode flag\n") ; you did something silly
	(s502 . "[info] Cannot change mode for other users\n") ; as above
	(ctcp_action . "ACTION %s->%s: %s\n") ; ctcp ACTION display
	(ctcp_clientinfo . "[query] CLIENTINFO from %s to %s\n") ; ctcp CLIENTINFO inform
	(ctcp_errmsg . "[query] ERRMSG from %s to %s\n") ; ctcp ERRMSG inform
	(ctcp_finger . "[query] FINGER from %s to %s\n") ; ctcp FINGER inform
	(ctcp_ping . "[query] PING from %s to %s\n") ; ctcp PING inform
	(ctcp_source . "[query] SOURCE from %s to %s\n") ; ctcp SOURCE inform
	(ctcp_time . "[query] TIME from %s to %s\n") ; ctcp TIME inform
	(ctcp_userinfo . "[query] USERINFO from %s to %s\n") ; ctcp USERINFO inform
	(ctcp_version . "[query] VERSION from %s to %s\n") ; ctcp VERSION inform
	(debug  . "[debug] %s\n")       ; displayed by debugging code
	(error . "[%s] %s\n")           ; server error message
	(invite . "[info] %s invites you to %s.\n") ; invite
	(kick . "[info] %s has been kicked from %s by %s\n") ; someone was peeved
	(kick_you . "[info] You have been kicked from %s by %s\n") ; loser
	(kill . "[info] You have been killed: %s") ; your time is up.
	(mode . "[info] %s has changed mode for %s: %s\n") ; MODE change
	(nick . "[info] %s has changed nick to %s\n") ; nick change
	(nosend . "[info] you have no current victim to send to\n") ; msg not sent
	(notice . "{%s%s} %s\n")        ; NOTICE
	(notice_you . "{%s} %s\n")      ; NOTICE sent to your nick
	(now-querying . "[info] Current victim is %s.\n") ; /query foo 
	(part_you . "[info] Leaving: %s\n") ; your part from channel message
	(part . "[info] %s has left %s\n") ;part from channel message
	(privmsg . "<%s%s> %s\n")       ; PRIVMSG
	(privmsg_you . "*%s* %s\n")     ; PRIVMSG sent to your nick
	(query . "[query] from %s to %s content %s\n") ; ctcp query
	(query_unknown . "is an unknown CTCP query") ; we don't grok this
	(query_unbalanced .		; someone is being lame
			  "[UNBALANCED query] from %s to %s content %s\n")
	(query_unbalanced_reply .
				"is an unbalanced CTCP query") ; odd number of ^A's
	(quit . "[info] %s stopped wasting time: %s\n") ; user signoff
	(reply . "[reply] from %s to %s content %s\n") ; ctcp reply
	(reply_unbalanced . "[UNBALANCED reply] from %s to %s content %s\n") ;weird
	(send . "(sent to %s)\n")       ; you sent a message/notice
	(server . "[server] %s\n")      ; unknown server message
	(signal . "[signal in %s]")     ; signal in echo area
	(topic . "[info] %s changed the topic on %s to: %s\n") ; topic message
	(wallops . "-%s- %s\n"))	; WALLOPS notice
      )
					;  "Association list of all text messages ZenIRC prints, for internationalization/customization")

;; existing clients messages are recycled here where possible, as it makes
;; it more likely that other clients will format them correctly.
(defvar zenirc-clientinfo-list
  '((ACTION . "ACTION contains action descriptions for atmosphere")
    (CLIENTINFO . "CLIENTINFO gives information about available CTCP commands")
    (ERRMSG . "ERRMSG returns error messages")
    (FINGER . "FINGER shows real name, and login name of user (idle time is not yet implemented in ZenIRC)")
    (PING . "PING returns the arguments it receives")
    ;; sojge sure is a wordy bastard.
    (SOURCE . "takes 0 arguments and returns a description of where to find the source code of the client. The description is made up out of zero or more lines followed by an end marker. Every line is a CTCP reply with the SOURCE keyword, a space, the name of a FTP-server, a colon, a directory name, a colon, and 0 or more file names. If no file names are given, all the files in the named directory are needed. The end marker contains just the keyword.")
    (TIME . "TIME tells you the time on the user's host")
    (USERINFO . "USERINFO returns user settable information")
    (VERSION . "VERSION shows client type, version, and environment"))
  "*Association list of CLIENTINFO ctcp help strings")

(defvar zenirc-clientinfo-string "ACTION CLIENTINFO ERRMSG FINGER PING SOURCE TIME USERINFO VERSION :Use CLIENTINFO <COMMAND> to get more specific information"
  "*CLIENTINFO Help string, showing list of CTCP commands supported")

(defvar zenirc-debug-mainloop nil "Set if you want to debug zenirc's main loop")
(defvar zenirc-debug-ignore nil "Set if you want to debug zenirc's ignore code")
(defvar zenirc-debug-signal nil "Set if you want to debug zenirc's signal code")
(defvar zenirc-debug-ctcp nil "Set if you want to debug zenirc's ctcp code")
(defvar zenirc-debug-commands nil "Set if you want to debug zenirc's command parser")


(setq zenirc-message-vector (make-vector 12 nil))

(setq zenirc-command-vector (make-vector 25 nil))


;;;###autoload
(defun zenirc-mode ()
  "Major mode for wasting time on IRC."
  (interactive)
  (let ((text-mode-hook nil))		; avoid calling text-mode-hook
    (text-mode))
  (setq mode-name "ZenIRC")
  (setq major-mode 'zenirc-mode)
  (setq mode-line-process '(": %s"))
  (use-local-map zenirc-mode-map))



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Code to handle connection to server
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;###autoload
(defun zenirc (&optional prefix)
  "Waste time on IRC"
  (interactive)
  ;; make local variables
  (let ((zenirc-buffer (if prefix 
                           (generate-new-buffer zenirc-buffer-name)
                         (get-buffer-create zenirc-buffer-name)))
        zenirc-process)
    (switch-to-buffer zenirc-buffer)
    (make-local-variable 'partialline)  ; unprocessed data read from socket
					; current channel talking to, or nil
    (make-local-variable 'zenirc-current-victim)
    (setq zenirc-current-victim nil)
    (zenirc-init-hooks)
    ;; open network connection
    ;; call zenirc-process-connect-hook, if it exists - this is handy for
    ;; proxy connections.
    (if (fboundp 'zenirc-process-connect-hook) 
	(zenirc-process-connect-hook)
      (setq zenirc-process (open-network-stream "zenirc" 
						zenirc-buffer 
						zenirc-server
						zenirc-port)))
    (set-marker (process-mark zenirc-process) (point-max))
    (set-process-buffer zenirc-process zenirc-buffer)
    (set-process-filter zenirc-process 'zenirc-filter)
    ;; Not implemented yet, and may turn out to be unnecessary.
					;(set-process-sentinel zenirc-process 'zenirc-sentinel)
    (zenirc-mode)
    (zenirc-logon-hook)))
;;
;; send nick, user@host information
;; NICK zenirc-nick
;; USER user-login-name@zenirc-server 1 1 :zenirc-name
;; we probably need to store the nickname in a var initialized to nil
;; so if we get a "nick already in use" message, we can tell if we have
;; a current nickname or not. 
;;
;; this function is called as a hook - zenirc-logon-hook - this is useful
;; for people who proxy.
;;
(defun zenirc-logon ()
  (let (proc (get-buffer-process (current-buffer)))
    (if zenirc-password
	(process-send-string proc (concat "PASS " zenirc-password "\n")))
    (process-send-string proc (concat "NICK " zenirc-nick "\n"))
    (process-send-string proc (concat "USER " (user-login-name)
				      "@" zenirc-server " 1 1 :" 
				      zenirc-name "\n"))))
;;
;; This function takes a chunk of text from the server, and any text
;; left over from the last chunk, and passes it to zenirc-parselines
;; to be interpreted.
;; 

(defun zenirc-filter (proc string)
  (let ((data (match-data)))
    (unwind-protect
	(progn
	  (save-excursion
	    (set-buffer (process-buffer proc))
	    (setq partialline (zenirc-parselines proc string partialline))))
      (store-match-data data))))

;;
;; This routine takes a bunch of text from the server, and any remnants
;; from the last bunch, and splits it into lines. The lines are passed
;; to zenirc-parseline to be parsed and then whatever needs to be done
;; for that server message is done.
;;

(defun zenirc-parselines (proc string partialline)
  (let ((serverstring (concat partialline string)) eol servermsg parsedmsg)
    (while (setq eol (string-match "\n" serverstring))
      (setq servermsg (substring serverstring 0 eol))
      (setq serverstring (substring serverstring (1+ eol) 
				    (length serverstring)))
      (if (not (zenirc-ignore-p servermsg))
	  (progn
	    (if (zenirc-signal-p servermsg) (zenirc-signal proc))
	    (let* ((parsedmsg (zenirc-parse-servermsg servermsg)) hook)
	      (setq hook (intern (concat "zenirc-server-" 
					 (aref parsedmsg 0) "-hook")))
	      (if zenirc-debug-mainloop
		  (progn
		    ;; useful for debugging hook code
		    (zenirc-display-string 
		     proc (format (cdr (assq 'debug zenirc-text-list))
				  (concat "hook:" (symbol-name hook))))
		    ;; useful for debugging server message parser
		    (zenirc-display-string 
		     proc (format (cdr (assq 'debug zenirc-text-list))
				  (concat "parsedmsg:" (prin1-to-string 
							parsedmsg))))))
	      ;; call the hook function, if it exists.
	      (if (fboundp hook) (funcall hook proc parsedmsg)
		;; just display message if no handler.
		(zenirc-display-string 
		 proc (format (cdr (assq 'server zenirc-text-list)) 
			      servermsg)))))
	(if zenirc-debug-ignore
	    (zenirc-display-string 
	     proc (format (cdr (assq 'debug zenirc-text-list))
			  (concat "ignored: " servermsg))))))
    serverstring))			; return the unprocessed partial line, if any.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; utility subroutines
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; ignore processing - msg is a server message sent to the client. Return
;; non-nil if it is to be ignored, nil if it is not to be ignored
;; thanks to mycroft@gnu.ai.mit.edu for showing how to do this efficiently -ben
;;
(defun zenirc-ignore-p (msg)
  (zenirc-recursive-match msg zenirc-ignorance-list))
;;
;; recursively match things
;;
(defun zenirc-recursive-match (msg regexps)
  (and regexps 
       (or (string-match (car regexps) msg)
	   (zenirc-recursive-match msg (cdr regexps)))))
;;
;; determine if an event is worthy of a signal
;;
(defun zenirc-signal-p (msg)
  (zenirc-recursive-match msg zenirc-signal-list))
;;
;; do a signal (pop up buffer, beep, whatever)
;; this should be a hook, and the default handler should be able to beep,
;; pop up the irc buffer, or both. Maybe mode-line notification also.
;;
(defun zenirc-signal (proc) 
  (save-excursion
    (set-buffer (process-buffer proc))
    (if zenirc-beep-on-signal (ding t))
    (message (cdr (assq 'signal zenirc-text-list)) (buffer-name))))
;;
;; return t if arg is a channel name, else nil
;;
(defun zenirc-channelp (arg)
  (or (eq (aref arg 0) ?#) (eq (aref arg 0) ?&)))
;;
;; replace an entry in zenirc-text-list, or add a new one if the key isn't
;; already in the list.
;;
(defun zenirc-set-text-list-ent (key text)
  (let ((listent (assq key zenirc-text-list)))
    (if listent 
	(setcdr listent text)
      (setq zenirc-text-list (cons (cons key text) zenirc-text-list)))))

;;
;; parse a server message into a list.
;; the list looks like ("msgtype" "sender" "to" "arg1" ... "argn")
;; 
(defun zenirc-parse-servermsg (string)
  (let ((posn (if (eq (aref string 0) ?:)
		  (string-match " " string)
		0))
	(msg zenirc-message-vector)
	(n 2))
    (fillarray msg nil)
    (aset msg 1 (if (eq posn 0)
		    (or zenirc-servername zenirc-server)
		  (substring string 1 posn)))
    (aset msg 0 (let* ((bposn (string-match "[^ ]" string posn))
		       (eposn (string-match " " string bposn)))
		  (setq posn (and eposn
				  (string-match "[^ ]" string eposn)))
		  (substring string bposn eposn)))
    (while (and posn
		(not (eq (aref string posn) ?:)))
      (aset msg n (let* ((bposn posn)
			 (eposn (string-match " " string bposn)))
		    (setq posn (and eposn
				    (string-match "[^ ]" string eposn)))
		    (substring string bposn eposn)))
      (setq n (1+ n)))
    (if posn
	(aset msg n (substring string (1+ posn))))
    msg))

(defun zenirc-display-string (proc string)
  (let (proc-mark region-begin window)
    (save-excursion
      (set-buffer (process-buffer proc))
      (setq proc-mark (process-mark proc)
	    region-begin (marker-position (process-mark proc))
	    ;; (Set region-begin to the location proc-mark pointed to at
	    ;; the start.) (this fix to a long standing bug contributed by
	    ;; rmtodd@servalan.servalan.com)
	    ;;
            ;; If process mark is at window start, insert-before-markers
            ;; will insert text off-window since it's also inserting before
            ;; the start window mark.  Make sure we can see the most recent
            ;; text.  (note: it's a buglet that this isn't necessary if
            ;; scroll-step is 0, but that works to our advantage since it
            ;; makes the filter a little faster.)
            window (and (/= 0 scroll-step)
                        (get-buffer-window (current-buffer))))
      (goto-char proc-mark)
      (insert-before-markers string)
      (goto-char region-begin)
      (while (search-forward "\C-m" proc-mark 'goto-end)
        (delete-char -1)))
    ;; Frob window-start outside of save-excursion so it works whether the
    ;; current buffer is the process buffer or not.
    (and window
         (>= (window-start window) region-begin)
         (set-window-start window region-begin 'noforce))))

;; this code is currently commented, but will see life in another
;; incarnation soon - ben
;;
;; Parse a command line into its constituent parts (words separated by
;; space). Return the number of parts and the vector of parts as a 
;; dotted pair. 
;; Note that we actually do the breaking on boundaries of space or newline,
;; assuming that there's usually a newline at the end of the string.  This
;; code probably could be improved.... ---- rmtodd@servalan.servalan.com
;(defun zenirc-parse-command (cmdline) 
;  (let ((cmd zenirc-command-vector)
;	(posn 0)
;	(n 0))
;    (fillarray cmd nil)
;    (while posn 
;      (aset cmd n (let* ((bposn posn)
;			 (eposn (string-match "[ \n]" cmdline bposn)))
;		    (setq posn (and eposn
;				    (string-match "[^ \n]" cmdline eposn)))
;		    (substring cmdline bposn eposn)))
;      (setq n (1+ n))) ; should probably check here if we're overruning the array. 
;    (cons n cmd)))
;;
;; parse a line into the first word and the rest.
;;
;; This returns ("word" . "rest"), where word means adjacent non-space 
;; characters. Any amount of whitespace is skipped after the first word, 
;; and "rest" is the rest of the line. If there is no "rest", a "rest"
;;  of "" is constructed.
;;
;; While the name indicates it's used to parse commands, it's also called
;; inside command handlers to further parse arguments.
;;
(defun zenirc-parse-command (cmdline)
  (if (eq nil (string-match "\\(^[^ ]*\\)  *\\(.*$\\)" cmdline))
      ;; no arguments - strip trailing return off of cmdline
      (cons (substring cmdline 0 (1- (length cmdline))) "")
    ;; there are arguments
    (cons (substring cmdline (match-beginning 1) (match-end 1))
	  (substring cmdline (match-beginning 2) (match-end 2)))))

;; Handle a zenirc / command typed by the user.  Check to see if there's a 
;; hook for the command and if so, execute the hook, otherwise just send the 
;; command line unaltered to the server.  
(defun zenirc-do-command (proc cmdline) 
  (let* ((parsedcmd (zenirc-parse-command cmdline))
	 (cmdname (car parsedcmd))
	 (hook (intern (concat "zenirc-command-" cmdname "-hook"))))
    (if zenirc-debug-commands 
	(progn
	  (zenirc-display-string proc
				 (format (cdr (assq 'debug zenirc-text-list))
					 (concat "cmdhook:" 
						 (symbol-name hook))))
	  (zenirc-display-string proc
				 (format (cdr (assq 'debug zenirc-text-list))
					 (concat "parsedcmd:" (prin1-to-string
							       parsedcmd))))))
    ;; Call the hook, if it's there.
    (if (fboundp hook) (funcall hook proc parsedcmd)
      ;; Otherwise, just send the unparsed command to the server.
      (process-send-string proc cmdline))))

;; Todo: Add command/input history. 
(defun zenirc-send-line ()
  "Send current line to IRC server."
  (interactive)
  (end-of-line)
  (insert "\n")
  (let* ((proc (get-buffer-process (current-buffer)))
         (proc-mark (process-mark proc))
         (string (buffer-substring proc-mark (point))))
    (set-marker proc-mark (point))
    (if (eq (aref string 0) ?/ )
	(zenirc-do-command proc (substring string 1))
      (if (string= zenirc-current-victim nil)
	  (zenirc-display-string
	   proc (format (cdr (assq 'nosend zenirc-text-list))))
	(process-send-string 
	 proc (concat "PRIVMSG " zenirc-current-victim
		      " :" string))
	(zenirc-display-string 
	 proc (format (cdr (assq 'send zenirc-text-list))
		      zenirc-current-victim))))))

;;
;; format a nick!user@host string (user & host are optional)
;;
;; By default, this is an identity funtion. You can hack it if you want
;; something fancier. (see examples in client distribution)
;;
(defun zenirc-format-nickuserhost (nickuserhost) nickuserhost)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; command handling subroutines
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; /query [victim]
;;
;; If we gave it an argument, set zenirc-current-victim to that arg.
;; If not, just display what zenirc-current-victim is.
;;
(defun zenirc-command-query (proc parsedmsg)
  (if (not (string= (cdr parsedmsg) ""))
      (setq zenirc-current-victim (cdr parsedmsg))
    ;; no arguments, just display who we're querying.
  (zenirc-display-string 
   proc (format (cdr (assq 'now-querying zenirc-text-list)) 
		zenirc-current-victim))))

;; /m victim message
;;
;; send a message to someone who is not the current victim
;;
(defun zenirc-command-m (proc parsedmsg)
  (let* ((parsedtext (zenirc-parse-command (cdr parsedmsg))))
    (process-send-string proc (concat "PRIVMSG " (car parsedtext)
				      " :" (cdr parsedtext) "\n"))))

;; /msg victim message
;;
;; send a message to someone who is not the current victim
;;
(defun zenirc-command-msg (proc parsedmsg)
  (zenirc-command-m-hook proc parsedmsg))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; server message handling subroutines
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; handle ERROR server message
;;
(defun zenirc-server-ERROR (proc parsedmsg)
  (zenirc-display-string proc 
			 (format (cdr (assq 'error zenirc-text-list))
				 (aref parsedmsg 1)
				 (aref parsedmsg 2))))

;;
;; INVITE - user invites you to channel
;;
(defun zenirc-server-INVITE (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 'invite zenirc-text-list))
	   (zenirc-format-nickuserhost-hook (aref parsedmsg 1))
	   (aref parsedmsg 3))))

;;
;; NICK change server message
;;
(defun zenirc-server-NICK (proc parsedmsg)
  (let ((from (aref parsedmsg 1)) (to (aref parsedmsg 2)))
    (zenirc-display-string 
     proc
     (format (cdr (assq 'nick zenirc-text-list))
	     (zenirc-format-nickuserhost-hook from)
	     to))
    (if (string= (downcase (zenirc-extract-nick from))
		 (downcase zenirc-nick))
	(setq zenirc-nick to))))

;;
;; NOTICE server message
;;
(defun zenirc-server-NOTICE (proc parsedmsg)
  (zenirc-privmsg-or-notice proc parsedmsg))

;;
;; JOIN server message
;; zenirc-current-victim is the current channel your msgs will go to.
;; probably need to keep an array of channels you are listening to, and
;; zenirc-current-victim is the one you are talking to (or nil for no
;; current channel). All these vars should be buffer-local.
;;
(defun zenirc-server-JOIN (proc parsedmsg)
  (let ((who (aref parsedmsg 1)))
    (if (string= (downcase (zenirc-extract-nick who)) (downcase zenirc-nick))
	(progn
	  (setq zenirc-current-victim (aref parsedmsg 2))
	  (zenirc-display-string proc 
				 (format (cdr (assq 'join_you zenirc-text-list))
					 zenirc-current-victim)))
      (zenirc-display-string proc
			     (format (cdr (assq 'join zenirc-text-list))
				     (zenirc-format-nickuserhost-hook who)
				     (aref parsedmsg 2))))))
;;
;; KICK - you have been removed from a channel
;; (KICK kicker #chan kickee [reason])
;;
(defun zenirc-server-KICK (proc parsedmsg)
  (if (not (string= (downcase zenirc-nick) (downcase (aref parsedmsg 3))))
      ;; someone else got kicked
      (zenirc-display-string
       proc
       (format (cdr (assq 'kick zenirc-text-list))
	       (aref parsedmsg 3) (aref parsedmsg 2)
	       (concat (zenirc-format-nickuserhost-hook (aref parsedmsg 1))
		       " - " (aref parsedmsg 4))))
    ;; you got kicked
    (zenirc-display-string 
     proc
     (format (cdr (assq 'kick_you zenirc-text-list))
	     (aref parsedmsg 2) (concat (aref parsedmsg 4) " - "
					(aref parsedmsg 5))))
    (if (string= (downcase zenirc-current-victim) (downcase (aref parsedmsg 2)))
	(setq zenirc-current-victim nil))))

;;
;; KILL - you have been killed
;;
(defun zenirc-server-KILL (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 'kill zenirc-text-list))
				 (aref parsedmsg 3))))

;;
;; MODE - channel or user mode change
;;
;; (MODE from <channel> {[+|-]|o|p|s|i|t|n|b|v} [<limit>] [<user>] [<ban mask>])
;; (MODE from <nickname> {[+|-]|i|w|s|o})
;; sum of channel b and o mode changes <= 3, so at most 5 args appear after the
;; the channel mode flags. (three b or o's, a k, and a l). Current actual 
;; server implementation seems to limit this to 4 args.
;;
(defun zenirc-server-MODE (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 'mode zenirc-text-list))
		(zenirc-format-nickuserhost-hook (aref parsedmsg 1))
		(aref parsedmsg 2)
		(concat (aref parsedmsg 3) " " (aref parsedmsg 4) " "
			(aref parsedmsg 5) " " (aref parsedmsg 6) " "
			(aref parsedmsg 7) " " (aref parsedmsg 8)))))

;;
;; PART - channel leave message
;;
(defun zenirc-server-PART (proc parsedmsg)
  (let ((channel (aref parsedmsg 2))
	(who (aref parsedmsg 1)))
    (if (string= (downcase (zenirc-extract-nick who)) (downcase zenirc-nick))
	(progn
	  (zenirc-display-string proc 
				 (format (cdr (assq 'part_you
						    zenirc-text-list))
					 channel))
	  (if zenirc-current-victim
	      (if (string= (downcase channel) (downcase zenirc-current-victim))
		  (setq zenirc-current-victim nil))))
      (zenirc-display-string proc
			     (format (cdr (assq 'part zenirc-text-list))
				     (zenirc-format-nickuserhost-hook 
				      who) channel)))))

;;
;; PING - server "are you alive" message
;;
(defun zenirc-server-PING (proc parsedmsg)
  (process-send-string proc (concat "PONG " (aref parsedmsg 3) "\n")))

;;
;; PRIVMSG server message
;;
(defun zenirc-server-PRIVMSG (proc parsedmsg)
  (zenirc-privmsg-or-notice proc parsedmsg))

;;
;; QUIT - someone (thankfully) left irc.
;;
(defun zenirc-server-QUIT (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 'quit zenirc-text-list))
				 (zenirc-format-nickuserhost-hook
				  (aref parsedmsg 1))
				 (aref parsedmsg 2))))

;;
;; TOPIC - someone set the topic on a channel
;;
(defun zenirc-server-TOPIC (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 'topic zenirc-text-list))
	   (zenirc-format-nickuserhost-hook (aref parsedmsg 1))
	   (aref parsedmsg 2) (aref parsedmsg 3))))

;;
;; WALLOPS - notice to operators
;;
					; [server] :goren1.u.washington.edu WALLOPS :Remote CONNECT *orst* 6667 from craig
(defun zenirc-server-WALLOPS (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 'wallops zenirc-text-list))
	   (aref parsedmsg 1) (aref parsedmsg 2))))

;;
;; 001 - welcome to irc
;;
(defun zenirc-server-001 (proc parsedmsg)
  (zenirc-display-string proc
			 (cdr (assq 's001 zenirc-text-list))))

;;
;; 002 - who is your server
;; hostname regexp [-a-zA-Z.0-9]
;;
(defun zenirc-server-002 (proc parsedmsg)
  (let ((str (aref parsedmsg 3)))
    (string-match 
     "Your host is \\([-a-zA-Z.0-9]+\\), running version \\(.*\\)"
     str)
    (zenirc-display-string 
     proc
     (format (cdr (assq 's002 zenirc-text-list))
	     (substring str (match-beginning 1) (match-end 1))
	     (substring str (match-beginning 2) (match-end 2))))))

;;
;; 003 - when this server was built
;;
(defun zenirc-server-003 (proc parsedmsg)
  (let ((str (aref parsedmsg 3)))
    (string-match "This server was created \\(.*\\)" str)
    (zenirc-display-string 
     proc
     (format (cdr (assq 's003 zenirc-text-list))
	     (substring str (match-beginning 1) (match-end 1))))))
;;
;; 004 - version and allowed modes information
;; these vars need to be defined and used at top level someday
;;
(defun zenirc-server-004 (proc parsedmsg)
  (setq zenirc-servername (aref parsedmsg 3))
  (setq zenirc-serverversion (aref parsedmsg 4))
  (setq zenirc-usermodes (aref parsedmsg 5))
  (setq zenirc-servermodes (aref parsedmsg 6)))

;;
;; 251 - :server 251 ZenIRC :There are x users and y invisible 
;; on z servers
;;
(defun zenirc-server-251 (proc parsedmsg)
  (let ((str (aref parsedmsg 3)))
    (string-match "There are \\([0-9]+\\) users and \\([0-9]+\\) invisible on \\([0-9]+\\) servers" str)
    (zenirc-display-string 
     proc
     (format (cdr (assq 's251 zenirc-text-list))
	     (substring str (match-beginning 1) (match-end 1))
	     (substring str (match-beginning 2) (match-end 2))
	     (substring str (match-beginning 3) (match-end 3))))))
;;
;; 252 - number of irc operators online
;;
(defun zenirc-server-252 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's252 zenirc-text-list))
		(aref parsedmsg 3))))
;;
;; 253 - number of "unknown" connections
;;
(defun zenirc-server-253 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's253 zenirc-text-list))
		(aref parsedmsg 3))))

;;
;; 254 - number of channels
;;
(defun zenirc-server-254 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's254 zenirc-text-list))
		(aref parsedmsg 3))))

;;
;; 255 - :server 255 ZenIRC :I have x clients and y servers
;;
(defun zenirc-server-255 (proc parsedmsg)
  (let ((str (aref parsedmsg 3)))
    (string-match "I have \\([0-9]+\\) clients and \\([0-9]+\\) servers" str)
    (zenirc-display-string 
     proc
     (format (cdr (assq 's255 zenirc-text-list))
	     (substring str (match-beginning 1) (match-end 1))
	     (substring str (match-beginning 2) (match-end 2))))))

;;
;; 301 - someone is /away
;;
(defun zenirc-server-301 (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 's301 zenirc-text-list))
				 (aref parsedmsg 3) (aref parsedmsg 4))))
;;
;; 302 - Userhost reply - RPL_USERHOST  ":[<reply>{<space><reply>}]"
;;
(defun zenirc-server-302 (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 's302 zenirc-text-list))
				 (aref parsedmsg 3))))

;;
;; 305 - you are not /away
;;
(defun zenirc-server-305 (proc parsedmsg)
  (zenirc-display-string proc
			 (cdr (assq 's305 zenirc-text-list))))
;;
;; 306 - you are /away
;;
(defun zenirc-server-306 (proc parsedmsg)
  (zenirc-display-string proc
			 (cdr (assq 's306 zenirc-text-list))))

;;
;; 311 - userinfo for /whois list
;;
(defun zenirc-server-311 (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 's311 zenirc-text-list))
				 (aref parsedmsg 3) (aref parsedmsg 4) 
				 (aref parsedmsg 5) (aref parsedmsg 7))))

;;
;; 312 - server part of /whois list
;;
(defun zenirc-server-312 (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 's312 zenirc-text-list))
				 (aref parsedmsg 3) (aref parsedmsg 4) 
				 (aref parsedmsg 5))))

;;
;; 313 - /whois list reply indicating irc operator
;; 
(defun zenirc-server-313 (proc parsedmsg)
  (zenirc-display-string proc 
			 (format (cdr (assq 's313 zenirc-text-list))
				 (aref parsedmsg 3))))

;;
;; 314 - /whowas reply
;;
(defun zenirc-server-314 (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 's314 zenirc-text-list))
				 (aref parsedmsg 3) (aref parsedmsg 4)
				 (aref parsedmsg 5) (aref parsedmsg 7))))

;;
;; 315 - end of /who list
;;
(defun zenirc-server-315 (proc parsedmsg)
  (zenirc-display-string proc
			 (cdr (assq 's315 zenirc-text-list))))

;;
;; 317 - /whois list idle time reply
;; needs to display years, months, days, hours, minutes, seconds, instead of just seconds.
;;
(defun zenirc-server-317 (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 's317 zenirc-text-list))
				 (aref parsedmsg 3) 
				 (string-to-int (aref parsedmsg 4)))))

;;
;; 318 - end of /whois list
;;
(defun zenirc-server-318 (proc parsedmsg)
  (zenirc-display-string proc
			 (cdr (assq 's318 zenirc-text-list))))

;;
;; 319 - what channels part of /whois list
;;
(defun zenirc-server-319 (proc parsedmsg)
  (zenirc-display-string proc 
			 (format (cdr (assq 's319 zenirc-text-list))
				 (aref parsedmsg 3) (aref parsedmsg 4))))
				 
;;
;; 321 - header for /list command
;;
(defun zenirc-server-321 (proc parsedmsg)
  (zenirc-display-string proc
			 (cdr (assq 's321 zenirc-text-list))))

;;
;; 322 - element returned by /list
;;
(defun zenirc-server-322 (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 's322 zenirc-text-list))
				 (aref parsedmsg 3) (aref parsedmsg 4)
				 (aref parsedmsg 5))))

;;
;; 323 - trailer for /list command
;;
(defun zenirc-server-323 (proc parsedmsg)
  (zenirc-display-string proc
			 (cdr (assq 's323 zenirc-text-list))))
;;
;; 324 - RPL_CHANNELMODEIS "<channel> <mode> <mode params>"
;;
(defun zenirc-server-324 (proc parsedmsg)
  (let ((arg (aref parsedmsg 5)))
    (zenirc-display-string
     proc
     (format (cdr (assq 's324 zenirc-text-list))
	     (aref parsedmsg 3) (aref parsedmsg 4) (if arg arg "")))))
;;
;; 331 - RPL_NOTOPIC "<channel> :No topic is set"
;;
(defun zenirc-server-331 (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 's331 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 332 - channel topic on join, etc.
;;
(defun zenirc-server-332 (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 's332 zenirc-text-list))
	   (aref parsedmsg 3) (aref parsedmsg 4))))

;;
;; 341 - invite reply
;;
(defun zenirc-server-341 (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 's341 zenirc-text-list))
	   (aref parsedmsg 3) (aref parsedmsg 4))))
;;
;; 342 - RPL_SUMMONING "<user> :Summoning user to IRC"
;;
(defun zenirc-server-342 (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 's342 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 351 - RPL_VERSION "<version>.<debuglevel> <server> :<comments>"
;;
(defun zenirc-server-351 (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 's351 zenirc-text-list))
	   (aref parsedmsg 3) (aref parsedmsg 4) (aref parsedmsg 5))))
;;
;; 352 - WHO reply
;;
(defun zenirc-server-352 (proc parsedmsg)
  (if (string= (aref parsedmsg 3) "Channel")
      ;; this is the header
      (zenirc-display-string proc
			     (format (cdr (assq 's352_header 
						zenirc-text-list))))
    ;; this is a reply
    (zenirc-display-string proc 
			   (format (cdr (assq 's352 zenirc-text-list))
				   (aref parsedmsg 7) (aref parsedmsg 8)
				   (aref parsedmsg 3) (aref parsedmsg 4)
				   (aref parsedmsg 5) (aref parsedmsg 9)))))


;;
;; 353 - name list after channel join or NAMES command
;;
(defun zenirc-server-353 (proc parsedmsg)
  (zenirc-display-string proc
			 (format (cdr (assq 's353 zenirc-text-list))
				 (aref parsedmsg 4) (aref parsedmsg 5))))

;;
;; 364 - RPL_LINKS "<mask> <server> :<hopcount> <server info>"
;;
(defun zenirc-server-364 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's364 zenirc-text-list))
		(aref parsedmsg 3) (aref parsedmsg 4)
		(aref parsedmsg 5))))
;;
;; 365 - RPL_ENDOFLINKS "<mask> :End of /LINKS list"
;;
(defun zenirc-server-365 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's365 zenirc-text-list))
		(aref parsedmsg 3))))
;;
;; 366 - after all ppl on channel displayed
;;
(defun zenirc-server-366 (proc parsedmsg) ())
;;
;; 367 - RPL_BANLIST "<channel> <banid>"
;;
(defun zenirc-server-367 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's367 zenirc-text-list))
		(aref parsedmsg 3) (aref parsedmsg 4))))
;;
;; 368 - RPL_RPL_ENDOFBANLIST
;;
(defun zenirc-server-368 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's368 zenirc-text-list)))))
;;
;; 369 - end of whowas
;;
(defun zenirc-server-369 (proc parsedmsg) ())
;;
;; 371 - RPL_INFO ":<string>"
;;
(defun zenirc-server-371 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's371 zenirc-text-list)) (aref parsedmsg 3))))
;;
;; 372 - motd line
;;
(defun zenirc-server-372 (proc parsedmsg)
  (zenirc-display-string
   proc (format (cdr (assq 's372 zenirc-text-list)) (aref parsedmsg 3))))
;;
;; 374 - RPL_ENDOFINFO ":End of /INFO list"
;;
(defun zenirc-server-374 (proc parsedmsg) ())
;;
;; 375 - start of /MOTD
;;
(defun zenirc-server-375 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's375 zenirc-text-list))))
;;
;; 376 - end of /MOTD
;;
(defun zenirc-server-376 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's376 zenirc-text-list))))
;;
;; 381 - RPL_YOUREOPER ":You are now an IRC operator"
;;
(defun zenirc-server-381 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's381 zenirc-text-list))))
;;
;; 382 - RPL_REHASHING "<config file> :Rehashing"
;;
(defun zenirc-server-382 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's382 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 391 - RPL_TIME "<server> :<string showing server's local time>"
;;
(defun zenirc-server-391 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's391 zenirc-text-list))
	   (aref parsedmsg 3) (aref parsedmsg 4))))
;;
;; 392 - RPL_USERSSTART ":UserID   Terminal  Host"
;;
(defun zenirc-server-392 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's392 zenirc-text-list))))
;;
;; 393 - RPL_USERS ":%-8s %-9s %-8s"
;;
(defun zenirc-server-393 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's393 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 394 - RPL_ENDOFUSERS ":End of users"
;;
(defun zenirc-server-394 (proc parsedmsg) ())
;;
;; 395 - RPL_NOUSERS ":Nobody logged in"
;;
(defun zenirc-server-395 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's395 zenirc-text-list))))
;;
;; 401 - no such nick/channel
;;
(defun zenirc-server-401 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's401 zenirc-text-list))
	   (aref parsedmsg 3))))

;;
;; 402 - no such server
;;
(defun zenirc-server-402 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's402 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 403 - ERRNOSUCHCHANNEL "<channel> :No such channel"
;;
(defun zenirc-server-403 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's403 zenirc-text-list))
	   (aref parsedmsg 3))))

;;
;; 404 - ERR_CANNOTSENDTOCHAN "<channel> :Cannot send to channel"
;;
(defun zenirc-server-404 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's404 zenirc-text-list))
	   (aref parsedmsg 3))))

;;
;; 405 - ERR_TOOMANYCHANNELS  "<channel_name> :You have joined too many channels"
;;
(defun zenirc-server-405 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's405 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 406 - ERR_WASNOSUCHNICK  "<channel_name> :There was no such nickname"
;;
(defun zenirc-server-406 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's406 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 407 - ERR_TOOMANYTARGETS "<target> :Duplicate recipients. No message delivered"
;;
(defun zenirc-server-407 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's407 zenirc-text-list))
	   (aref parsedmsg 3))))

;;
;; 409 - ERR_NOORIGIN ":No origin specified"
;;
(defun zenirc-server-409 (proc parsedmsg) ())

;;
;; 411 - ERR_NORECIPIENT ":No recipient given (<command>)"
;;
(defun zenirc-server-411 (proc parsedmsg)
  (zenirc-display-string
   proc
   (cdr (assq 's411 zenirc-text-list))))

;;
;; 412 - ERR_NOTEXTTOSEND ":No text to send"
;; you sent a message w/o any text
;;
(defun zenirc-server-412 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's412 zenirc-text-list))))

;;
;; 413 - ERR_NOTOPLEVEL "<mask> :No toplevel domain specified"
;;
(defun zenirc-server-413 (proc parsedmsg)
  (zenirc-display-string 
   proc (format (cdr (assq 's413 zenirc-text-list)) (aref parsedmsg 3))))
;;
;; 414 - ERR_WILDTOPLEVEL "<mask> :Wildcard in toplevel domain"
;;
(defun zenirc-server-414 (proc parsedmsg)
  (zenirc-display-string 
   proc (format (cdr (assq 's414 zenirc-text-list)) (aref parsedmsg 3))))

;;
;; 421 - server detected error in what you sent
;;
(defun zenirc-server-421 (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 's421 zenirc-text-list)) (aref parsedmsg 3))))

;;
;; 422 - ERR_NOMOTD ":MOTD File is missing"
;;
(defun zenirc-server-422 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's422 zenirc-text-list))))

;;
;; 423 - ERR_NOADMININFO "<server> :No administrative info available"
;;
(defun zenirc-server-423 (proc parsedmsg)
  (zenirc-display-string 
   proc
   (format (cdr (assq 's423 zenirc-text-list)) (aref parsedmsg 3))))
;;
;; 424 ERR_FILEERROR ":File error doing <file op> on <file>"
;;
(defun zenirc-server-424 (proc parsedmsg)
  (zenirc-display-string proc (aref parsedmsg 3)))
;;
;; 431     ERR_NONICKNAMEGIVEN ":No nickname given"
;;
(defun zenirc-server-431 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's431 zenirc-text-list))))
;;
;; 432 -  ERR_ERRONEUSNICKNAME "<nick> :Erroneus nickname"
;;
(defun zenirc-server-432 (proc parsedmsg)
  (zenirc-display-string proc 
			 (format (cdr (assq 's432 zenirc-text-list))
				 (aref parsedmsg 3))))
;;
;; 433 - ERR_NICKNAMEINUSE "<nick> :Nickname is already in use"
;;
(defun zenirc-server-433 (proc parsedmsg)
  (zenirc-display-string proc 
			 (format (cdr (assq 's433 zenirc-text-list))
				 (aref parsedmsg 3))))
;;
;; 436 - ERR_NICKCOLLISION "<nick> :Nickname collision KILL"
;;
(defun zenirc-server-436 (proc parsedmsg)
  (zenirc-display-string proc 
			 (format (cdr (assq 's436 zenirc-text-list))
				 (aref parsedmsg 3))))
;;
;; 441 - ERR_USERNOTINCHANNEL "<nick> <channel> :They aren't on that channel"
;;
(defun zenirc-server-441 (proc parsedmsg)
  (zenirc-display-string proc 
			 (format (cdr (assq 's441 zenirc-text-list))
				 (aref parsedmsg 3) (aref parsedmsg 4))))

;;
;; 442 - You are not on that channel
;;
(defun zenirc-server-442 (proc parsedmsg)
  (zenirc-display-string proc 
			 (format (cdr (assq 's442 zenirc-text-list))
				 (aref parsedmsg 3))))

;;
;; 443 - already on channel invite error
;;
(defun zenirc-server-443 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's443 zenirc-text-list))
	   (aref parsedmsg 3) (aref parsedmsg 4))))
;;
;; 444 - ERR_NOLOGIN "<user> :User not logged in"
;;
(defun zenirc-server-444 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's444 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 445 - ERR_SUMMONDISABLED ":SUMMON has been disabled"
;;
(defun zenirc-server-445 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's445 zenirc-text-list))))
;;
;; 446 - ERR_USERSDISABLED ":USERS has been disabled"
;;
(defun zenirc-server-446 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's446 zenirc-text-list))))
;;
;; 451 - ERR_NOTREGISTERED ":You have not registered"
;;
(defun zenirc-server-451 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's451 zenirc-text-list))))
;;
;; 461 - server detected error - not enough parameters
;;
(defun zenirc-server-461 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's461 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 462 - ERR_ALREADYREGISTRED  ":You may not reregister"
;;
(defun zenirc-server-462 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's462 zenirc-text-list))))
;;
;; 463 - ERR_NOPERMFORHOST ":Your host isn't among the privileged"
;;
(defun zenirc-server-463 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's463 zenirc-text-list))))
;;
;; 464 - ERR_PASSWDMISMATCH ":Password incorrect"
;;
(defun zenirc-server-464 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's464 zenirc-text-list))))
;;
;; 465 - ERR_YOUREBANNEDCREEP ":You are banned from this server"
;;
(defun zenirc-server-465 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's465 zenirc-text-list))))
;;
;; 467 - ERR_KEYSET "<channel> :Channel key already set"
;;
(defun zenirc-server-467 (proc parsedmsg)
  (zenirc-display-string 
   proc 
   (format (cdr (assq 's467 zenirc-text-list)) (aref parsedmsg 3))))
;;
;; 471 - ERR_CHANNELISFULL "<channel> :Cannot join channel (+l)"
;;
(defun zenirc-server-471 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's471 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 472 - ERR_UNKNOWNMODE "<char> :is unknown mode char to me"
;;
(defun zenirc-server-472 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's472 zenirc-text-list))
	   (aref parsedmsg 3))))

;;
;; 473 - ERR_INVITEONLYCHAN "<channel> :Cannot join channel (+i)"
;;
(defun zenirc-server-473 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's473 zenirc-text-list))
	   (aref parsedmsg 3))))

;;
;; 474 - ERR_BANNEDFROMCHAN "<channel> :Cannot join channel (+b)"
;;
(defun zenirc-server-474 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's474 zenirc-text-list))
	   (aref parsedmsg 3))))

;;
;; 475 - ERR_BADCHANNELKEY "<channel> :Cannot join channel (+k)"
;;
(defun zenirc-server-475 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's475 zenirc-text-list))
	   (aref parsedmsg 3))))

;;
;; 481 - ERR_NOPRIVILEGES ":Permission Denied- You're not an IRC operator"
;;
(defun zenirc-server-481 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's481 zenirc-text-list))))
;;
;; 482 - ERR_CHANOPRIVSNEEDED "<channel> :You're not channel operator"
;;
(defun zenirc-server-482 (proc parsedmsg)
  (zenirc-display-string
   proc
   (format (cdr (assq 's482 zenirc-text-list))
	   (aref parsedmsg 3))))
;;
;; 483 - ERR_CANTKILLSERVER ":You cant kill a server!"
;;
(defun zenirc-server-483 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's483 zenirc-text-list))))
;;
;; 491 - ERR_NOOPERHOST ":No O-lines for your host"
;;
(defun zenirc-server-491 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's491 zenirc-text-list))))
;;
;; 501 - ERR_UMODEUNKNOWNFLAG ":Unknown MODE flag"
;;
(defun zenirc-server-501 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's501 zenirc-text-list))))
;;
;; 502 - ERR_USERSDONTMATCH ":Cant change mode for other users"
;;
(defun zenirc-server-502 (proc parsedmsg)
  (zenirc-display-string proc (cdr (assq 's502 zenirc-text-list))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; PRIVMSG/NOTICE and CTCP handling
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun zenirc-privmsg-or-notice (proc parsedmsg)
  (let ((from (aref parsedmsg 1))	; who the message is from
	(to (aref parsedmsg 2))		; who the message is to
	(text (aref parsedmsg 3))	; the text of the message
	(type (aref parsedmsg 0)))	; privmsg or notice
    ;; see if privmsg or notice contains ctcp
    (if (not (string-match "\C-a" text))
	(zenirc-format-privmsg-or-notice type from to text proc)
      ;;
      ;; message contains ctcp. break it up into ctcp and non-ctcp parts
      ;; and handle each.
      ;;
      (let ((index 0) (ctcp nil) (str "") (len (length text)))
	(while (< index len)
	  (if (char-equal (aref text index) ?\C-a)
	      ;; we hit a control-a - deal with it.
	      (progn
		(if (not (string= str ""))
		    ;; we have a string
		    (if ctcp
			;; we are in a ctcp message
			(progn
			  (zenirc-handle-ctcp type from to str proc)
			  (setq str ""))
		      ;; we are in a regular message
		      (zenirc-format-privmsg-or-notice type from to str proc)
		      (setq str ""))
		  ;; we do not have a string
		  (if (and (not (= index 0)) ctcp)
		      ;; some leper sent a zero length message
		      (zenirc-handle-ctcp type from to str proc)))
		(setq ctcp (not ctcp))) ; toggle ctcp state
	    ;; we have a regular character
	    (setq str (concat str (char-to-string (aref text index)))))
	  (setq index (1+ index))
	  (if (and (= index len) ctcp)
	      ;; someone sent us an ill-formed ctcp message
	      (zenirc-unbalanced-ctcp type from to str proc)))))))
;;
;; handle a ctcp message 
;;
(defun zenirc-handle-ctcp (type from to str proc)
  (let (hook parsedctcp)
    (setq parsedctcp (zenirc-parse-ctcp str))
    (if (string= type "PRIVMSG")
	;; it was a privmsg
	(progn
	  (setq hook (intern 
		      (concat "zenirc-ctcp-query-" (car parsedctcp) "-hook")))
	  (if zenirc-debug-ctcp
	      (zenirc-display-string 
	       proc (format (cdr (assq 'debug zenirc-text-list)) 
			    (prin1-to-string hook))))
	  (if (fboundp hook) 
	      ;; call the hook if it exists
	      (funcall hook proc parsedctcp from to)
	    ;; maybe send an error if it doesn't
	    (if zenirc-send-ctcp-errmsg-on-unknown
		(zenirc-ctcp-errmsg 
		 type (zenirc-format-nickuserhost-hook from) to str 
		 (cdr 
		  (assq 'query_unknown zenirc-text-list)) proc))
	    (zenirc-display-string 
	     proc (format (cdr (assq 'query zenirc-text-list))
			  (zenirc-format-nickuserhost-hook from)
			  to str))))
      ;; it was a notice
      (setq hook (intern 
		  (concat "zenirc-ctcp-reply-" (car parsedctcp) "-hook")))
      (if zenirc-debug-ctcp
	  (zenirc-display-string 
	   proc (format (cdr (assq 'debug zenirc-text-list)) 
			(prin1-to-string hook))))
      (if (fboundp hook) 
	  ;; call the hook if it exists
	  (funcall hook proc parsedctcp from to)
	;; display reply
	(zenirc-display-string 
	 proc (format (cdr (assq 'reply zenirc-text-list))
		      (zenirc-format-nickuserhost-hook from) to str))))))

;;
;; spew a ctcp error
;;
(defun zenirc-ctcp-errmsg (type from to str whine proc)
  (let ((from-nick (zenirc-extract-nick from)))
    (process-send-string 
     proc (concat "NOTICE " from-nick " :\C-aERRMSG " 
		  str " :" whine "\C-a\n"))))
  
;;
;; handle an unbalanced ctcp message 
;;
(defun zenirc-unbalanced-ctcp (type from to str proc)
  (if (string= type "PRIVMSG")
      ;; it was a privmsg
      (progn (if zenirc-send-ctcp-errmsg-on-unbalanced
		 (zenirc-ctcp-errmsg 
		  type from to str 
		  (cdr (assq 
			'query_unbalanced_reply zenirc-text-list))
		  proc))
	     (zenirc-display-string
	      proc (format (cdr (assq 'query_unbalanced zenirc-text-list))
			   (zenirc-format-nickuserhost-hook from) to str)))
    
    ;; it was a notice
    (zenirc-display-string 
     proc (format (cdr (assq 'reply_unbalanced zenirc-text-list)) 
		  (zenirc-format-nickuserhost-hook from) to str))))

;;
;; this should be moved to utility page, it's here to make it easy to
;; debug
;; returns nil if the argument is a server name
;;
(defun zenirc-extract-nick (nickuserhost)
  (let ((posn (string-match "[.!@]" nickuserhost)))
    (if (and posn
	     (eq (aref nickuserhost posn) ?.))
	nil
      (substring nickuserhost 0 posn))))

;;
;; parse a ctcp string into (query/reply . args). Args are "" if they are
;; not present.
;;
(defun zenirc-parse-ctcp (str)
  (let (ctcp args)
    (if (eq nil (string-match "\\(^[^ ]*\\) \\(.*$\\)" str))
	;; no arguments
	(cons str "")
      ;; there are arguments
      (cons (substring str (match-beginning 1) (match-end 1))
	    (substring str (match-beginning 2) (match-end 2))))))

;;
;; format a PRIVMSG or NOTICE and insert it in the zenirc process buffer
;;
(defun zenirc-format-privmsg-or-notice (type from to text proc)
  (let ((from (zenirc-format-nickuserhost-hook from)))
    (zenirc-display-string
     proc
     ;; see if message is to you explicitly, or to a bunch of people
     (if (string= (downcase to) (downcase zenirc-nick))
	 ;; message is to you
	 (format (cdr (assq (if (string= type "PRIVMSG") 
				'privmsg_you
			      'notice_you) zenirc-text-list))
		 from text)
       ;; message is to a bunch of people
       (format (cdr (assq (if (string= type "PRIVMSG")
			      'privmsg
			    'notice) zenirc-text-list))
	       from to text)))))
;;;
;;; ctcp handlers
;;;

;;
;; handler for a ctcp ACTION query
;;
(defun zenirc-ctcp-query-ACTION (proc parsedctcp from to)
  (zenirc-display-string
   proc 
   (format (cdr (assq 'ctcp_action zenirc-text-list))
	   (zenirc-format-nickuserhost-hook from) to (cdr parsedctcp))))
;;
;; handler for a ctcp PING query
;;
(defun zenirc-ctcp-query-PING (proc parsedctcp from to)
  (process-send-string
   proc (concat "NOTICE "
		(zenirc-extract-nick from)
		" :\C-aPING " (cdr parsedctcp) "\C-a\n"))
  (if zenirc-verbose-ctcp
      (zenirc-display-string
       proc
       (format (cdr (assq 'ctcp_ping zenirc-text-list))
	       (zenirc-format-nickuserhost-hook from) to))))

;;
;; handler for a ctcp VERSION query
;;
(defun zenirc-ctcp-query-VERSION (proc parsedctcp from to)
  (process-send-string 
   proc (concat "NOTICE " 
		(zenirc-extract-nick from)
		" :\C-aVERSION ZenIRC alpha : $Revision: 1.72 $ : in gnu emacs "
		emacs-version "\C-a\n"))
  (if zenirc-verbose-ctcp 
      (zenirc-display-string
       proc
       (format (cdr (assq 'ctcp_version zenirc-text-list)) 
	       (zenirc-format-nickuserhost-hook from) to))))

;;
;; handler for a ctcp USERINFO query
;;
(defun zenirc-ctcp-query-USERINFO (proc parsedctcp from to)
  (process-send-string 
   proc (concat "NOTICE " 
		(zenirc-extract-nick from)
		" :\C-aUSERINFO :" zenirc-userinfo "\C-a\n"))
  (if zenirc-verbose-ctcp 
      (zenirc-display-string
       proc
       (format (cdr (assq 'ctcp_userinfo zenirc-text-list)) 
	       (zenirc-format-nickuserhost-hook from) to))))

;;
;; handler for a ctcp SOURCE query
;;
(defun zenirc-ctcp-query-SOURCE (proc parsedctcp from to)
  (process-send-string 
   proc (concat "NOTICE " 
		(zenirc-extract-nick from)
		" :\C-aSOURCE :" zenirc-source "\C-a\n"))
  (if zenirc-verbose-ctcp 
      (zenirc-display-string
       proc
       (format (cdr (assq 'ctcp_source zenirc-text-list)) 
	       (zenirc-format-nickuserhost-hook from) to))))
;;
;; handler for a ctcp CLIENTINFO query
;;
(defun zenirc-ctcp-query-CLIENTINFO (proc parsedctcp from to)
  (if (string= (cdr parsedctcp) "")
      (process-send-string 
       proc (concat "NOTICE " 
		    (zenirc-extract-nick from)
		    " :\C-aCLIENTINFO " zenirc-clientinfo-string "\C-a\n"))
    (let ((helpstring 
	   (cdr (assq (intern (cdr parsedctcp)) zenirc-clientinfo-list))))
      (if helpstring
	  (process-send-string
	   proc (concat "NOTICE " (zenirc-extract-nick from)
			" :\C-aCLIENTINFO " helpstring "\C-a\n"))
	(zenirc-ctcp-errmsg 
	 "PRIVMSG" from to "CLIENTINFO" 
	 (concat (cdr parsedctcp) " is not a valid function") proc))))
  (if zenirc-verbose-ctcp 
      (zenirc-display-string
       proc
       (format (cdr (assq 'ctcp_clientinfo zenirc-text-list)) 
	       (zenirc-format-nickuserhost-hook from) to))))

;;
;; handler for a ctcp ERRMSG query
;;
(defun zenirc-ctcp-query-ERRMSG (proc parsedctcp from to)
  (process-send-string 
   proc 
   (concat "NOTICE " 
	   (zenirc-extract-nick from)
	   " :\C-aERRMSG" 
	   (if (not (string= "" (cdr parsedctcp)))
	       (concat
		" "
		(cdr parsedctcp)
		" :No error"))
	   "\C-a\n"))
  (if zenirc-verbose-ctcp 
      (zenirc-display-string
       proc
       (format (cdr (assq 'ctcp_errmsg zenirc-text-list)) 
	       (zenirc-format-nickuserhost-hook from) to))))
;;
;; handler for a ctcp FINGER query
;;
(defun zenirc-ctcp-query-FINGER (proc parsedctcp from to)
  (process-send-string 
   proc (concat "NOTICE " 
		(zenirc-extract-nick from)
		" :\C-aFINGER :" zenirc-fingerdata "\C-a\n"))
  (if zenirc-verbose-ctcp 
      (zenirc-display-string
       proc
       (format (cdr (assq 'ctcp_finger zenirc-text-list)) 
	       (zenirc-format-nickuserhost-hook from) to))))
;;
;; handler for a ctcp TIME query
;;
(defun zenirc-ctcp-query-TIME (proc parsedctcp from to)
  (process-send-string 
   proc (concat "NOTICE " 
		(zenirc-extract-nick from)
		" :\C-aTIME :" (current-time-string) "\C-a\n"))
  (if zenirc-verbose-ctcp 
      (zenirc-display-string
       proc
       (format (cdr (assq 'ctcp_time zenirc-text-list)) 
	       (zenirc-format-nickuserhost-hook from) to))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; hooks
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; set up hooks
;;
(defun zenirc-init-hooks ()
  (if (fboundp 'zenirc-startup-hook) (zenirc-startup-hook))
  (mapcar 
   'zenirc-init-hook
   '(zenirc-ctcp-query-ACTION-hook
     zenirc-ctcp-query-CLIENTINFO-hook
     zenirc-ctcp-query-ERRMSG-hook
     zenirc-ctcp-query-FINGER-hook
     zenirc-ctcp-query-PING-hook
     zenirc-ctcp-query-SOURCE-hook
     zenirc-ctcp-query-TIME-hook
     zenirc-ctcp-query-USERINFO-hook
     zenirc-ctcp-query-VERSION-hook
     zenirc-server-ERROR-hook
     zenirc-server-INVITE-hook
     zenirc-server-JOIN-hook
     zenirc-server-KICK-hook
     zenirc-server-KILL-hook
     zenirc-server-MODE-hook
     zenirc-server-NICK-hook
     zenirc-server-NOTICE-hook
     zenirc-server-PART-hook
     zenirc-server-PING-hook
     zenirc-server-PRIVMSG-hook
     zenirc-server-QUIT-hook
     zenirc-server-TOPIC-hook
     zenirc-server-WALLOPS-hook
     zenirc-server-001-hook
     zenirc-server-002-hook
     zenirc-server-003-hook
     zenirc-server-004-hook
     zenirc-server-251-hook
     zenirc-server-252-hook
     zenirc-server-253-hook
     zenirc-server-254-hook
     zenirc-server-255-hook
     zenirc-server-301-hook
     zenirc-server-302-hook
     zenirc-server-305-hook
     zenirc-server-306-hook
     zenirc-server-311-hook
     zenirc-server-312-hook
     zenirc-server-313-hook
     zenirc-server-314-hook
     zenirc-server-315-hook
     zenirc-server-317-hook
     zenirc-server-318-hook
     zenirc-server-319-hook
     zenirc-server-321-hook
     zenirc-server-322-hook
     zenirc-server-323-hook
     zenirc-server-324-hook
     zenirc-server-331-hook
     zenirc-server-332-hook
     zenirc-server-341-hook
     zenirc-server-342-hook
     zenirc-server-351-hook
     zenirc-server-352-hook
     zenirc-server-353-hook
     zenirc-server-364-hook
     zenirc-server-365-hook
     zenirc-server-366-hook
     zenirc-server-367-hook
     zenirc-server-368-hook
     zenirc-server-369-hook
     zenirc-server-371-hook
     zenirc-server-372-hook
     zenirc-server-374-hook
     zenirc-server-375-hook
     zenirc-server-376-hook
     zenirc-server-381-hook
     zenirc-server-382-hook
     zenirc-server-391-hook
     zenirc-server-392-hook
     zenirc-server-393-hook
     zenirc-server-394-hook
     zenirc-server-395-hook
     zenirc-server-401-hook
     zenirc-server-402-hook
     zenirc-server-403-hook
     zenirc-server-404-hook
     zenirc-server-405-hook
     zenirc-server-406-hook
     zenric-server-407-hook
     zenirc-server-409-hook
     zenirc-server-411-hook
     zenirc-server-412-hook
     zenirc-server-413-hook
     zenirc-server-414-hook
     zenirc-server-421-hook
     zenirc-server-422-hook
     zenirc-server-423-hook
     zenirc-server-424-hook
     zenirc-server-431-hook
     zenirc-server-432-hook
     zenirc-server-433-hook
     zenirc-server-436-hook
     zenirc-server-441-hook
     zenirc-server-442-hook
     zenirc-server-443-hook
     zenirc-server-444-hook
     zenirc-server-445-hook
     zenirc-server-446-hook
     zenirc-server-451-hook
     zenirc-server-461-hook
     zenirc-server-462-hook
     zenirc-server-463-hook
     zenirc-server-464-hook
     zenirc-server-465-hook
     zenirc-server-467-hook
     zenirc-server-471-hook
     zenirc-server-472-hook
     zenirc-server-473-hook
     zenirc-server-474-hook
     zenirc-server-475-hook
     zenirc-server-481-hook
     zenirc-server-482-hook
     zenirc-server-483-hook
     zenirc-server-491-hook
     zenirc-server-501-hook
     zenirc-server-502-hook
     zenirc-command-m-hook
     zenirc-command-msg-hook
     zenirc-command-query-hook
     zenirc-format-nickuserhost-hook
     zenirc-logon-hook)))

(defun zenirc-init-hook (hook)
  (if (not (fboundp hook))
      (fset hook (intern (substring (prin1-to-string hook) 0 -5)))))


;; End of zenirc.el
