home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / interfaces / zephyr / zephyr.el < prev   
Encoding:
Text File  |  1993-01-24  |  32.2 KB  |  947 lines

  1. ;;; zephyr.el  an interface to the zephyr message system
  2. ;;; Copyright (C) 1992, 1993 Scott Draves (spot@cs.cmu.edu)
  3. ;;;
  4. ;;; This program is free software; you can redistribute it and/or modify
  5. ;;; it under the terms of the GNU General Public License as published by
  6. ;;; the Free Software Foundation; either version 2 of the License, or
  7. ;;; (at your option) any later version.
  8. ;;;
  9. ;;; This program is distributed in the hope that it will be useful,
  10. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. ;;; GNU General Public License for more details.
  13. ;;;
  14. ;;; You should have received a copy of the GNU General Public License
  15. ;;; along with this program; if not, write to the Free Software
  16. ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  17.  
  18. ;;; This is version 2.0 (now polished a bit).  The primary advantage
  19. ;;; is that it no longer
  20. ;;; forks off a zwrite to do sends - instead, it uses a new version of
  21. ;;; tzc which is capable of sending as well as receiving.  This code
  22. ;;; was contributed by Nick Thompson (nix@cs.cmu.edu).
  23. ;;; You *will* need the new version of tzc (should be included).
  24.  
  25. ;;; If you write C programs which talk to emacs, you should check out
  26. ;;; lread.h and lread.c in the tzc program.  These provide a
  27. ;;; convenient way to communicate structured data.
  28.  
  29. ;;; send and receive zephyrgrams with a convenient and flexible
  30. ;;; interface.  some people say the interface feels like IRC.
  31. ;;; here's a quick feature list:
  32. ;;;    asynchronously send and receive messages from a single buffer
  33. ;;;    handles instances, multiple destinations
  34. ;;;    aliases (can translate one name to many)
  35. ;;;    "lazy" beep for notification
  36. ;;;    sender/receiver history
  37. ;;;    filter messages based on sender and/or instance
  38. ;;;    multiple buffers, each with different filters
  39. ;;;    highly customizeable
  40. ;;;
  41. ;;; there's some support for encrypted instances, but it no longer
  42. ;;; works.  to use, copy the following lines into your .emacs:
  43. ;;;
  44. ;;;  (load-library "zephyr")
  45. ;;;  (zephyr-new-buffer)
  46. ;;;
  47. ;;; this mode requires the tzc program which should have come with the
  48. ;;; distribution.  the distrubution is available by ftp from
  49. ;;; hopeless.mess.cs.cmu.edu:/usr/spot/pub/zephyr.tar.Z, and should
  50. ;;; show up in the elisp archive at some point.
  51. ;;;
  52. ;;; thanks to todd kaufmann, mark eichin, nick thompson, and hank wan
  53. ;;; for their contributions.
  54. ;;;
  55. ;;; i am always interested in bug reports and improvements to this
  56. ;;; code.  feel free to send mail.
  57.  
  58.  
  59. ;;; TODO
  60. ;;; 1) subscripts:  when an unrecognized instance arrives, ask if you
  61. ;;; want to read it.  remember subs in a file (.zephyr.el).  should
  62. ;;; tie into encryption.  or just have simple functions for adding and
  63. ;;; removing instances to the regexps, esp one that sez "ignore this
  64. ;;; instance for the next 30 (by default, ^U changes) minutes"
  65. ;;; 2) make send to end-of-line (?)
  66. ;;; 3) consider a fn and binding for jump to last window to receive a
  67. ;;; zgram.
  68. ;;; 4) make the hook list and beep time be locals. (?)
  69. ;;; 5) make the exposure be a sexp we send to tzc rather than a
  70. ;;; command line option.  eliminate other options same way.
  71. ;;; 6) if you send to multiple destinations, the "... done" messages
  72. ;;; cover each other up, so you can't really see all of them.
  73.  
  74. ;;; CHANGES
  75. ;;; Thu Jan 21 - removed zwgc code to emulate tzc because it can't
  76. ;;; send messages (handle the input).  added a save-excursion to
  77. ;;; zephyr-do-insert so that point isn't moved in buffers that aren't
  78. ;;; visible when they receive a message.  rewrote
  79. ;;; zephyr-notify-with-scroll so it doesn't scroll the current window.
  80. ;;; Thu Jan  7 - replaced ` with cons due to incompatible versions of
  81. ;;; ` floating around.
  82. ;;; Fri Jan  1 - ugh, the recipient of "(foo)" is now "".
  83. ;;; zephyr-make-text-recipient now handles "foo(bar)".  and handle the
  84. ;;; additional information in "to" tag of tzcspew sent messages.
  85. ;;; added * to docstring of a bunch of variables.  removed
  86. ;;; zephyr-require-tzcspew. when receiving a message, formats as 
  87. ;;; "foo(bar)", rather than "foo (bar)"
  88. ;;; Sun Dec 13 - parse names a little differently:  "foo" now goes to
  89. ;;; the user foo.  "(foo)" goes to everyone reading instance foo, and
  90. ;;; "foo(bar)" goes to just user foo as instance bar.  aliases are now
  91. ;;; recursive.
  92. ;;; Sun Dec 13 - merged in changes to handle new tzc, run tzc with
  93. ;;; pipe rather than pty.  call this 2.0 because the new tzc is
  94. ;;; slightly incompatible.
  95. ;;; Mon Dec  7 - handles class MESSAGE messages that have the wrong
  96. ;;; number of fields, as can happen if the sender embeds a NULL.
  97. ;;; Mon Nov 16 - removed old code to parse output of vanilla zwgc
  98. ;;; Sat Oct 24 - if any function in the hook list returns nil, then do
  99. ;;; not call the rest of the hooks.
  100. ;;; 22/8/92 - added sentinel so when zephyr receiver dies, we are
  101. ;;; notified.  remove sentinel before restarting.
  102. ;;; 29/7/92 - added zwgc description file that emulates tzc
  103. ;;; 26/7/92 - fixed zephyr-add-timestamp, it wasn't returning the msg
  104. ;;; 11/7/92 - fixed: M-p and M-n raise error if no messages sent yet.
  105. ;;; split zephyr-notify into zephyr-notify-with-beep,
  106. ;;; zephyr-notify-with-message, and zephyr-notify-with-scroll. added
  107. ;;; zephyr-touch.  calls zephyr-touch from zephyr-insert (rather than
  108. ;;; notify).
  109. ;;; 9/7/92 - replaced a bunch of random hooks with zephyr-hook-list,
  110. ;;; including reorganizing the code, various small improvements.
  111. ;;; commented out dangling encryption stuff.  tzc now stamps with date
  112. ;;; string rather than integer so that it is human readable in the
  113. ;;; log, and also cuz emacs ints aren't big enuf. added todd's
  114. ;;; delete-messages function.  removed that bury crap.
  115.  
  116.  
  117. ;;; the next 4 variables control what messages you see
  118.  
  119. (defvar zephyr-senders-ignore-regexp nil
  120.   "*if this regexp matches the sender, then ignore the message.  it's
  121. typical to put yourself in this list so that you don't get what you
  122. send to instances you also read.  to ignore nobody, use nil.")
  123.  
  124. (defvar zephyr-senders-accept-regexp nil
  125.   "*only accept messages from users who match this regexp.  nil means
  126. match everybody.")
  127.  
  128. (defvar zephyr-instances-ignore-regexp nil
  129.   "*if this regexp mathces the instance, then ignore the message. use
  130. nil to ignore nobody.")
  131.  
  132. (defvar zephyr-instances-accept-regexp nil
  133.   "*only accept messages from instances that match this regexp.  nil
  134. means match all instances")
  135.  
  136.  
  137. (defvar zephyr-hook-list
  138.   '(zephyr-parse
  139.     zephyr-dispatch
  140.     zephyr-parse-fields
  141.     zephyr-add-banner
  142.     zephyr-insert
  143.     zephyr-notify-with-message
  144.     zephyr-notify-with-beep
  145.     zephyr-notify-with-scroll)
  146.   "*a list of functions to call to process an incoming message.  a lot
  147. of customization can be done by changing this list.  for example, to
  148. visibly timestamp messages, add the function zephyr-add-timestamp next
  149. to zephyr-add-banner.")
  150.  
  151.  
  152. (defvar zephyr-send-divider "<<< "
  153.   "pattern to mark the beginning of the message to be sent.
  154. everything between it and point is considered part of the message.
  155. everything between it and the beginning of the line is considered a
  156. list of recipients.")
  157.  
  158. (defvar zephyr-send-divider-regexp "<<<\\( \\|$\\)"
  159.   "regexp to match zephyr-send-divider.  it pays to be a bit lenient.")
  160.  
  161. (defvar zephyr-receive-program '("/afs/cs.cmu.edu/user/spot/bin/tzc"
  162.                  "-e" "NET-ANNOUNCED")
  163.   "*list containing program and its options. invoked to receive
  164. zephyr-grams.")
  165.  
  166. (defvar zephyr-kerberos-authenticate t
  167.   "*if true, then the client is requested to use kerberos
  168. authentication when sending messages.  othewise use no authentication.")
  169.  
  170. (defvar zephyr-aliases '(("stooges" . ("larry" "curly" "moe")))
  171.   "*alist of recursive aliases.")
  172.  
  173. (defvar zephyr-instance-xlate-hook 'zephyr-instance-xlate
  174.   "call this function with each recipient.  returns a list of options
  175. for the send program.  typically, it recognizes instances.")
  176.  
  177. (defvar zephyr-receive-divider ">>> "
  178.   "string that appears immediately before the message body of incoming
  179. zephyr-grams.")
  180.  
  181. (defvar zephyr-signature (concat (getenv "USER") "\n")
  182.   "*signature to use on outgoing zephyrgrams")
  183.  
  184. (defvar zephyr-unauth-string "(NOAUTH)"
  185.   "string that appears next to messages that are from unauthenticated
  186. sources.")
  187.  
  188. (defvar zephyr-failed-string "(FORGED)"
  189.   "string that appears next to messages whose authentication attempt
  190. failed.")
  191.  
  192. (defvar zephyr-lazy-beep-time 120
  193.   "*beep, unless a msg has been received since this many seconds ago.
  194. if this is nil, then never beep.")
  195.  
  196. (defvar zephyr-log-buffer-limit 32000
  197.   "*prevent the zephyr-log-buffer from expanding beyond this many
  198. characters.  the buffer acts as a queue -- text from the beginning is
  199. thrown away as new messages arrive.  the value nil means that the
  200. buffer will grow without bound.")
  201.  
  202. (defvar zephyr-buffer-limit 32000
  203.   "*prevent the *zephyr* buffer from expanding beyond this many
  204. characters.  the buffer acts as a queue -- text from the beginning is
  205. thrown away as new messages arrive.  the value nil means that the
  206. buffer will grow without bound.")
  207.  
  208. (defvar zephyr-client-eom-string "\0"
  209.   "string matching the end of the output from the zephyr client
  210. receiving messages.  for zwgc this is usually ^G.")
  211.  
  212. (defvar zephyr-client-bom-string "\1"
  213.   "string matching the beginning of the output from the zephyr client
  214. receiving messages.  must be different from
  215. zephyr-client-eom-string.")
  216.  
  217. ; (defvar zephyr-encrypt-program "crypt"
  218. ;   "invoke this program to encrypt messages.")
  219.  
  220. ; (defvar zephyr-decrypt-program "crypt"
  221. ;  "invoke this program to decrypt messages.")
  222.  
  223. ; (defvar zephyr-cypher-is-binary t
  224. ;   "true if the cypher-text generated by zephyr-encrypt-program is
  225. ; binary.")
  226.  
  227. ; (defvar zephyr-passwords '(("an-encrypted-instance" .
  228. ;                 "some-password"))
  229. ;   "alist of instance-password pairs.  if the password is nil, the user
  230. ; is prompted when it is needed.  the passwords can be cleared with
  231. ; zephyr-clear-passwords. encrpytion doesn't work.")
  232.  
  233.  
  234. ; how to remove buffers when they are killed? rebind C-xk?!
  235. (defvar zephyr-buffer-list nil
  236.   "list of buffers that are receiving zephyrgrams")
  237.  
  238. (defun zephyr-new-buffer (&optional name)
  239.   "create a new buffer for reading and sending zephyrgrams.  the
  240. buffer is named *zephyr-NAME*, where NAME is printed representation of
  241. the argument, or just *zephyr* if NAME is nil, or not provided."
  242.   (interactive)
  243.   (let ((name (if name
  244.           (concat "*zephyr-" name "*")
  245.         "*zephyr*")))
  246.     (switch-to-buffer name)
  247.     (zephyr-mode)
  248.     (zephyr-compose "")
  249.     (setq zephyr-buffer-list (cons (current-buffer)
  250.                    zephyr-buffer-list))))
  251.  
  252. (defun zephyr-match (s n)
  253.   (substring s
  254.          (match-beginning n)
  255.          (match-end n)))
  256.   
  257. ;;; convert a name to an instance/user pair
  258. (defun zephyr-instance-xlate (name)
  259.   (let ((l (assoc name zephyr-aliases))
  260.     (n (string-match "(\\(.*\\))" name)))
  261.     (cond (l (apply 'append (mapcar 'zephyr-instance-xlate (cdr l))))
  262.       (n (list (cons (zephyr-match name 1)
  263.              (if (= n 0) "" (substring name 0 n)))))
  264.       (t (list (cons "PERSONAL" name))))))
  265.  
  266. ;;; convert string to list of strings, basically seperate it into
  267. ;;; words.  space, tab, and comma are considered seperators.
  268. (defun zephyr-send-make-recipient-list (recipient)
  269.   (if (string-match "[ \t,]*\\([^ \t,]+\\)" recipient)
  270.       (let* ((beg (match-beginning 1))
  271.          (end (match-end 1))
  272.          (name (substring recipient beg end)))
  273.     (append (funcall zephyr-instance-xlate-hook name)
  274.         (zephyr-send-make-recipient-list
  275.          (substring recipient end))))
  276.     nil))
  277.  
  278. (defun zephyr-get-instance (l)
  279.   (if l
  280.       (if (equal (car l) "-i")
  281.       (car (cdr l))
  282.     (zephyr-get-instance (cdr l)))
  283.     nil))
  284.  
  285. (defvar zephyr-last-recipient "nobody")
  286.  
  287. (defun zephyr-make-text-recipient (recip)
  288.   (cond ((string= (car recip) "PERSONAL")
  289.      (cdr recip))
  290.     (t (concat (cdr recip) "(" (car recip) ")"))))
  291.  
  292. (defun zephyr-send-and-compose ()
  293.   "send the zephyrgram before point, and start composing another one."
  294.   (interactive)
  295.   (unwind-protect
  296.       (let* ((cur-buf (current-buffer))
  297.          (log-buffer-size (progn (set-buffer zephyr-log-buffer)
  298.                      (buffer-size))))
  299.     (set-buffer cur-buf)
  300.     (save-excursion
  301.       (let* ((end-msg (point))
  302.          (pat (concat "^.+\\("
  303.                    zephyr-send-divider-regexp
  304.                    "\\)"))
  305.          (end-recipient (progn (re-search-backward pat)
  306.                        (match-beginning 1)))
  307.          (start-recipient (match-beginning 0))
  308.          (start-msg (match-end 0))
  309.          (msg (buffer-substring start-msg end-msg))
  310.          (recipient (buffer-substring start-recipient end-recipient))
  311.          (recip-list (zephyr-send-make-recipient-list recipient))
  312.          (text-recip-list (mapconcat 'zephyr-make-text-recipient
  313.                          recip-list " ")))
  314.         (message (concat "Sending to " text-recip-list "..."))
  315.         (setq zephyr-last-recipient recipient)
  316.         (zephyr-touch-name recipient)
  317.         ;; (let* ((inst (zephyr-get-instance recip-list))
  318.         ;;   (a (assoc inst zephyr-passwords)))
  319.         ;; (if a (progn
  320.         ;;      (setq msg (zephyr-en/decrypt msg (cdr a) t))
  321.         ;;    (if zephyr-cypher-is-binary
  322.         ;;  (setq msg (zephyr-btoa msg))))))
  323.         (zephyr-send msg recip-list)))
  324.     (zephyr-limit-buffer-size zephyr-buffer-limit)
  325.     (set-buffer zephyr-log-buffer)
  326.     (if (not (equal log-buffer-size (buffer-size)))
  327.         (progn
  328.           (pop-to-buffer zephyr-log-buffer)
  329.           (error "zwrite complained")))
  330.     (set-buffer cur-buf))
  331.     ;; this is protected, so in case of error sending, we still set up
  332.     ;; for the next one
  333.     (zephyr-compose "")))
  334.  
  335. (defun zephyr-send (msg to-list)
  336.   ;; WARNING! WARNING! WARNING!
  337.   ;; if you send a message without at least two parts in the "message"
  338.   ;; field, any zwgc receiving the message will die a silent death,
  339.   ;; and people will probably whine at you instead of at the author
  340.   ;; of zwgc.
  341.   (let ((str (format "%s\n"
  342.              (list '(tzcfodder . send)
  343.                (cons 'auth zephyr-kerberos-authenticate)
  344.                '(class . MESSAGE)
  345.                (cons 'recipients to-list)
  346.                (cons 'message (list zephyr-signature msg))))))
  347.     ;; (print str (get-buffer "*scratch*"))
  348.     (process-send-string zephyr-process str)))
  349.  
  350. ;;; go to the end of the buffer, add a new header
  351. (defun zephyr-send-setup (recipient)
  352.   (goto-char (point-max))
  353.   (if (not (bolp))
  354.       (insert "\n"))
  355.   (insert (concat recipient zephyr-send-divider)))
  356.  
  357. (defun zephyr-compose (recipient)
  358.   "compose a zephyr-gram to be sent.  prompts for recipient, if none
  359. is given, use last recipient."
  360.   (interactive "sTo: ")
  361.   (if (not (equal mode-name "Zephyr"))
  362.       (if zephyr-buffer-list
  363.       (pop-to-buffer (car zephyr-buffer-list))
  364.     (error "no zephyr buffers")))
  365.   (if (equal recipient "")
  366.       (zephyr-send-setup zephyr-last-recipient)
  367.     (zephyr-send-setup recipient)))
  368.  
  369.  
  370. ;;; valid values for tzcspew:
  371. ;;;   message - an incoming message
  372. ;;;   error - an error from tzc
  373. ;;;   sent - message successfully sent
  374.  
  375. ;;; add most of the tags to the message alist.  since tzc prints out
  376. ;;; messages in lisp-readable from, this is mostly just a call to
  377. ;;; read.  if you were using zwgc instead, you would have to do
  378. ;;; something horrible, something like what's in the preceding
  379. ;;; comments.
  380. ;;;
  381. ;;; the tags in the alist contain at least:
  382. ;;;
  383. ;;;   message - a list of strings, one per field
  384. ;;;   instance - a string, often "PERSONAL"
  385. ;;;   auth - a symbol, either 'yes, 'no, or 'failed
  386. ;;;   kind - a symbol, usually 'acked
  387. ;;;   port - an integer, the sender's port
  388. ;;;   class - a symbol, usually 'MESSAGE
  389. ;;;   opcode - a symbol, usually either nil or 'PING
  390. ;;;   sender - a string, the login name of the sender
  391. ;;;   recipient - a string, either your login name or NIL
  392. ;;;   fromhost - a string, the hostname where the msg orginated
  393. ;;;   time - a string, just like from current-time-string
  394. ;;;
  395. ;;; these tags will sometimes be added, depending on what happens to the
  396. ;;; msg and what its contents are:
  397. ;;;
  398. ;;;   buffers - a list of buffers, where the message appeared
  399. ;;;   print-as - a string, how we should display it
  400. ;;;   signature - a string, from the sender (class MESSAGE)
  401. ;;;   body - a string, the message (class MESSAGE)
  402. ;;;   host - a string, (class LOGIN)
  403. ;;;   when - a string, (class LOGIN)
  404. ;;;   tty - a string, (class LOGIN)
  405.  
  406. (defun zephyr-parse (msg)
  407.   (let* ((r (cdr (assq 'raw-source msg)))
  408.      (buf (car r))
  409.      (start (cadr r))
  410.      (end (caddr r)))
  411.     (set-buffer buf)
  412.     (goto-char start)
  413.     (read buf)))
  414.  
  415. (setq zephyr-tzcspew-dispatch-table
  416.       '((message . zephyr-pass)
  417.     (error . zephyr-error)
  418.     (sent . zephyr-sent)
  419.     (not-sent . zephyr-not-sent)))
  420.  
  421. ;;; dispatch the message based on the tzcspew tag
  422. (defun zephyr-dispatch (msg)
  423.   (let* ((spewtag (cdr (assq 'tzcspew msg))))
  424.     (if (null spewtag)
  425.     (error "zephyr: missing tzcspew tag - obsolete version of tzc?"))
  426.     (let* ((spewfun (cdr (assq spewtag zephyr-tzcspew-dispatch-table))))
  427.       (funcall spewfun msg))))
  428.  
  429. (defun zephyr-pass (msg)
  430.   msg)
  431.  
  432. (defun zephyr-error (msg)
  433.   (let ((err (cdr (assq 'message msg))))
  434.     (if (null err)
  435.     (message "zephyr: tzc error")
  436.       (message (format "zephyr: tzc error %s" err))))
  437.   (beep)
  438.   nil)
  439.  
  440. (defun zephyr-sent (msg)
  441.   (let ((to (cdr (assq 'to msg))))
  442.     (message (concat "Sending to "
  443.              (zephyr-make-text-recipient to)
  444.              "... done")))
  445.   nil)
  446.  
  447. (defun zephyr-not-sent (msg)
  448.   (let ((to (cdr (assq 'to msg))))
  449.     (message (concat "zephyr: send to "
  450.              (zephyr-make-text-recipient to)
  451.              " failed")))
  452.   (beep)
  453.   nil)
  454.  
  455. (defvar zephyr-insert-p 'zephyr-default-insert-p
  456.   "*predicate that returns true if its argument, a msg-alist, should
  457. appear in the current buffer")
  458.  
  459. ;;; for each zephyr buffer, check if this msg should appear in it.  if
  460. ;;; so, insert it.
  461. (defun zephyr-insert (msg)
  462.   (let ((buffers nil)
  463.     (loop zephyr-buffer-list))
  464.     (while loop
  465.       (if (buffer-name (car loop))
  466.       (progn
  467.         (set-buffer (car loop))
  468.         (if (funcall zephyr-insert-p msg)
  469.         (progn
  470.           (zephyr-do-insert msg)
  471.           (zephyr-touch msg)
  472.           (setq buffers (cons (car loop) buffers))))
  473.         (setq loop (cdr loop)))))
  474.     (cons (cons 'buffers buffers) msg)))
  475.  
  476. (defun zephyr-default-insert-p (msg)
  477.   (let ((instance (cdr (assq 'instance msg)))
  478.     (sender  (cdr (assq 'sender msg)))
  479.     (opcode  (cdr (assq 'opcode msg)))
  480.     (msg-text (cdr (assq 'print-as msg))))
  481.  
  482.     (not (or (eq 'PING opcode)
  483.  
  484.          (and zephyr-instances-ignore-regexp
  485.           (string-match zephyr-instances-ignore-regexp
  486.                 instance))
  487.      
  488.          (and zephyr-senders-ignore-regexp
  489.           (string-match zephyr-senders-ignore-regexp
  490.                      sender))
  491.      
  492.          (and zephyr-instances-accept-regexp
  493.           (not (string-match zephyr-instances-accept-regexp
  494.                      instance)))
  495.          
  496.          (and zephyr-senders-accept-regexp
  497.           (not (string-match zephyr-senders-accept-regexp
  498.                      sender)))))))
  499.  
  500. ;;; really stick it in the current buffer.  guarantee newline termination
  501. (defun zephyr-do-insert (msg)
  502.   (let ((msg-banner (cdr (assq 'banner msg)))
  503.     (msg-text   (cdr (assq 'print-as msg))))
  504.     (save-excursion
  505.       (goto-char (point-max))
  506.       (re-search-backward zephyr-send-divider-regexp (point-min) t)
  507.       (re-search-backward "^")
  508.       (insert msg-banner
  509.           msg-text)
  510.       (if (or (= 0 (length msg-text))
  511.           (not (equal "\n"
  512.               (substring msg-text
  513.                      (- (length msg-text) 1)))))
  514.       (insert "\n")))))
  515.  
  516.  
  517. ;;; add the string used to "introduce" a message.
  518. (defun zephyr-add-banner (msg)
  519.   (let ((instance (cdr (assq 'instance msg)))
  520.     (sender  (cdr (assq 'sender msg)))
  521.     (auth (cdr (assq 'auth msg))))
  522.     (cons (cons 'banner
  523.         (concat (zephyr-make-text-recipient (cons instance sender))
  524.             (cond ((eq auth 'yes) "")
  525.                   ((eq auth 'failed) zephyr-failed-string)
  526.                   ((eq auth 'no) zephyr-unauth-string))
  527.             zephyr-receive-divider))
  528.       msg)))
  529.  
  530. ;;; an example function you can add to the pipeline that
  531. ;;; timestamps messages.
  532. (defun zephyr-add-timestamp (msg)
  533.   (let ((banner (assq 'banner msg)))
  534.     (if banner
  535.     (setcdr banner (concat (cdr banner)
  536.                    "("
  537.                    (substring (current-time-string) 11 16)
  538.                    ")"))))
  539.   msg)
  540.  
  541. ;;; this one comes in handy too...
  542. (defun zephyr-dump (msg)
  543.   (print msg (get-buffer "*scratch*"))
  544.   msg)
  545.  
  546. ; give names to the various fields and add them to the alist.  also
  547. ; add the print-as tag, containing the printed rep.
  548. (defun zephyr-parse-fields (msg)
  549.   (let ((class (cdr (assq 'class msg)))
  550.     (sender (cdr (assq 'sender msg)))
  551.     (fields (cdr (assq 'message msg))))
  552.     (cond (; in messages, the first field is a signature, and the
  553.        ; second is the message body.
  554.        (eq class 'MESSAGE)
  555.        
  556.        (let* ((len (length fields))
  557.           (sig (cond ((= 2 len)
  558.                   (string-match "\\(.*\\)\n*"
  559.                         (car fields))
  560.                   (zephyr-match (car fields) 1))
  561.                  ((= 1 len)
  562.                   sender)
  563.                  (t sender)))
  564.           (body (cond ((= 2 len) (cadr fields))
  565.                   ((= 1 len) (car fields))
  566.                   (t (format "malformed message: %s" fields)))))
  567.          (append (list (cons 'print-as body)
  568.                (cons 'signature sig)
  569.                (cons 'body body))
  570.              msg)))
  571.       (; in logins, the fields are host, when, and tty.
  572.        (eq class 'LOGIN)
  573.        (let ((host (nth 0 fields))
  574.          (when (nth 1 fields))
  575.          (tty (nth 2 fields)))
  576.          (append (list (cons 'print-as
  577.                  (concat "on " host " at " when))
  578.                (cons 'host host)
  579.                (cons 'when when)
  580.                (cons 'tty tty))
  581.              msg)))
  582.       (t (cons (cons 'print-as
  583.              (mapconcat '(lambda (x) x)
  584.                     fields "\n"))
  585.            msg)))))
  586.  
  587.  
  588. (defun zephyr-lazy-beep (now delay)
  589.   (let ((then zephyr-lazy-beep-last))
  590.     (setq zephyr-lazy-beep-last now) ; horrid global var
  591.     (if (and delay (or (not then)
  592.                (> (time-difference-in-seconds then now)
  593.               delay)))
  594.     (beep))))
  595.  
  596.  
  597.  
  598. ;
  599. ;; convert binary to ascii, slow stupid, simple.
  600. ;(defun zephyr-btoa (s)
  601. ;  (mapconcat '(lambda (c) (int-to-string c)) s " "))
  602. ;
  603. ;; convert ascii to binary, slow stupid, simple.
  604. ;; returns nil if there is a formatting error
  605. ;(defun zephyr-atob (s)
  606. ;  (save-excursion
  607. ;    (let ((src (generate-new-buffer " atob-src"))
  608. ;      ans)
  609. ;      (set-buffer src)
  610. ;      (insert "(" s ")")
  611. ;      (goto-char (point-min))
  612. ;      (setq ans
  613. ;        (condition-case ERR
  614. ;        (mapconcat 'char-to-string (read src) "")
  615. ;          (error nil)))
  616. ;      (kill-buffer src)
  617. ;      ans)))
  618. ;
  619. ;; en/decrype S using KEY.  EN is true means encrypt, otherwise
  620. ;; decrypt.
  621. ;(defun zephyr-en/decrypt (s key en)
  622. ;  (save-excursion
  623. ;    (let ((in (generate-new-buffer " endecrypt-in"))
  624. ;      (out (generate-new-buffer " endecrypt-out"))
  625. ;      (pgm (if en zephyr-encrypt-program
  626. ;         zephyr-decrypt-program)))
  627. ;      (set-buffer in)
  628. ;      (insert s)
  629. ;      (call-process-region (point-min) (point-max)
  630. ;               pgm t out nil key)
  631. ;      (kill-buffer in)
  632. ;      (set-buffer out)
  633. ;      (let ((b (buffer-string)))
  634. ;    (kill-buffer out)
  635. ;    b))))
  636. ;
  637.  
  638.  
  639. (defun zephyr-notify-with-message (msg)
  640.   (let ((buffers (cdr (assq 'buffers msg))))
  641.     (if buffers
  642.     (message (concat "received "
  643.              (or (downcase (cdr (assq 'instance msg)))
  644.                  "instanceless")
  645.              " zephyrgram from "
  646.              (or (cdr (assq 'signature msg))
  647.                  (cdr (assq 'sender msg))
  648.                  "???")))))
  649.   msg)
  650.  
  651. (defun zephyr-notify-with-beep (msg)
  652.   (let ((buffers (cdr (assq 'buffers msg)))
  653.     (instance (cdr (assq 'instance msg)))
  654.     (time (cdr (assq 'time msg))))
  655.     (if buffers
  656.     (if (not (equal (downcase instance) "urgent"))
  657.         (zephyr-lazy-beep time zephyr-lazy-beep-time)
  658.       (beep) (beep) (beep))))
  659.   msg)
  660.  
  661. ;;; if the window where the messages appears is active, move so the
  662. ;;; end (where the message is) is visible
  663. (defun zephyr-buffer-show-end (buf)
  664.   (let* ((win (get-buffer-window buf)))
  665.     (if win
  666.     (progn
  667.       (set-buffer buf)
  668.       (if (pos-visible-in-window-p (point-max) win)
  669.           nil
  670.         (goto-char (point-max))
  671.         (vertical-motion (- 2 (window-height win)))
  672.         (set-window-start win (point)))))))
  673.  
  674.  
  675. (defun zephyr-notify-with-scroll (msg)
  676.   (let ((buffers (cdr (assq 'buffers msg)))
  677.     (curser-win (selected-window)))
  678.     (while buffers
  679.       (let* ((buf (car buffers))
  680.          (win (get-buffer-window buf)))
  681.     (if (and win
  682.          (not (eq win curser-win)))
  683.         (progn
  684.           (set-buffer buf)
  685.           (goto-char (point-max))
  686.           (vertical-motion (- 2 (window-height win)))
  687.           (set-window-start win (point)))))
  688.       (setq buffers (cdr buffers))))
  689.   msg)
  690.  
  691.  
  692. (defun zephyr-limit-buffer-size (lim)
  693.   (let ((max (point-max)))
  694.     (if (and lim (> max lim))
  695.     (delete-region (point-min) (- max lim)))))
  696.  
  697. (defun zephyr-receive-sentinel (proc sig)
  698.   (let ((status (process-status proc)))
  699.     (if (or (eq 'exit status)
  700.         (eq 'signal status))
  701.     (progn
  702.       (message "zephyr-receive died")
  703.       (pop-to-buffer zephyr-log-buffer)))))
  704.  
  705. (defun zephyr-do-message-hooks (msg)
  706.   (let ((loop zephyr-hook-list))
  707.     (while (and loop msg) ; bail if the message becomes nil
  708.       (setq msg (funcall (car loop) msg))
  709.       (setq loop (cdr loop)))))
  710.  
  711. (defun zephyr-receive-filter (process string)
  712.   (save-excursion
  713.     (set-buffer zephyr-log-buffer)
  714.     (let ((start (point-max))
  715.       (done nil))
  716.       (goto-char start)
  717.       (insert string)
  718.       (goto-char start)
  719.       (while (search-forward zephyr-client-eom-string
  720.                  (point-max) t)
  721.     (let ((end (point)))
  722.       (forward-char -1)
  723.       (if (search-backward zephyr-client-bom-string
  724.                    (point-min) t)
  725.           (forward-char 1)
  726.         (goto-char (point-min)))
  727.       (zephyr-do-message-hooks
  728.        (list (list 'raw-source
  729.                zephyr-log-buffer (point) end)))
  730.       (set-buffer zephyr-log-buffer)
  731.       (goto-char end)))
  732.       (zephyr-limit-buffer-size zephyr-log-buffer-limit))))
  733.  
  734.  
  735. (defun zephyr-restart-receiver ()
  736.   "kill and start another receiver process.  this is a good thing to do if
  737. your kerberos tickets expire, causing all messages authentication to
  738. appear failed."
  739.   (interactive)
  740.   (set-process-sentinel zephyr-process nil)
  741.   (delete-process zephyr-process)
  742.   (zephyr-start-receiver))
  743.  
  744. (defun zephyr-start-receiver ()
  745.   (setq zephyr-lazy-beep-last nil)
  746.   (setq zephyr-process
  747.     (let ((process-connection-type nil))
  748.       (apply 'start-process
  749.          "zephyr-receive" zephyr-log-buffer
  750.          zephyr-receive-program)))
  751.   (process-kill-without-query zephyr-process)
  752.   (set-process-sentinel zephyr-process 'zephyr-receive-sentinel)
  753.   (set-process-filter zephyr-process 'zephyr-receive-filter))
  754.  
  755. (defvar zephyr-previous-names nil
  756.   "doubly linked list of names of destinations and sources of
  757. zephyrgrams previously sent and received.  most recent is first.  no
  758. duplicates.")
  759.  
  760. (defun cadr (l) (car (cdr l)))
  761. (defun cddr (l) (cdr (cdr l)))
  762. (defun cdddr (l) (cdr (cdr (cdr l))))
  763. (defun cdadr (l) (cdr (car (cdr l))))
  764. (defun caddr (l) (car (cdr (cdr l))))
  765.  
  766. (defun zephyr-touch (msg)
  767.   "touch the name(s) appearing in msg"
  768.   (let ((sender (cdr (assq 'sender msg)))
  769.     (instance (cdr (assq 'instance msg))))
  770.     (zephyr-touch-name sender)
  771.     (if (not (equal "PERSONAL" instance))
  772.     (zephyr-touch-name (concat "(" instance ")")))))
  773.  
  774. (defun zephyr-touch-name (name)
  775.   "move NAME to head zephyr-previous-names, add if not already there."
  776.   (if zephyr-previous-names
  777.       (if (not (equal name (car zephyr-previous-names)))
  778.       (progn
  779.         (let ((h (cddr zephyr-previous-names)))
  780.           (while (not (eq h zephyr-previous-names))
  781.         (if (equal (car h) name)
  782.             (progn
  783.               (setcar (cdddr h) (cadr h))
  784.               (setcdr (cdadr h) (cddr h))
  785.               (setq h zephyr-previous-names))
  786.           (setq h (cddr h)))))
  787.         (let ((n (cons name (cons zephyr-previous-names
  788.                       (cddr zephyr-previous-names)))))
  789.           (setcar (cdddr n) n)
  790.           (setcdr (cdadr n) n)
  791.           (setq zephyr-previous-names n))))
  792.     (let ((n (cons name (cons nil nil))))
  793.       (setcar (cdr n) n)
  794.       (setcdr (cdr n) n)
  795.       (setq zephyr-previous-names n))))
  796.  
  797. (defun zephyr-replace-destination (name)
  798.   "replace the current destination with NAME"
  799.   (save-excursion
  800.    (re-search-backward zephyr-send-divider-regexp)
  801.    (let ((end-dest (point)))
  802.      (re-search-backward "^")
  803.      (delete-region (point) end-dest)
  804.      (insert name))))
  805.  
  806. (defun zephyr-next-destination (arg)
  807.   "cycle forward through previous senders/destinations"
  808.   (interactive "*p")
  809.   (if zephyr-previous-names
  810.       (if (= arg 0)
  811.       (zephyr-replace-destination (car zephyr-previous-names))
  812.     (progn
  813.       (setq zephyr-previous-names (cddr zephyr-previous-names))
  814.       (zephyr-next-destination (- arg 1))))))
  815.  
  816. (defun zephyr-previous-destination (arg)
  817.   "cycle backward through previous senders/destinations"
  818.   (interactive "*p")
  819.   (if zephyr-previous-names
  820.       (if (= arg 0)
  821.       (zephyr-replace-destination (car zephyr-previous-names))
  822.     (progn
  823.       (setq zephyr-previous-names (cadr zephyr-previous-names))
  824.       (zephyr-previous-destination (- arg 1))))))
  825.  
  826.  
  827.  
  828. (defun zephyr-delete-messages-from (inst)
  829.   "delete all messages from a particular person that appear after
  830. point.  takes a regexp."
  831.   (interactive "sInstance name (regexp): ")
  832.   (let* ((receive-divider-regexp (regexp-quote zephyr-receive-divider))
  833.      (kill (concat "\\("
  834.                inst  ;; why not:  (regexp-quote inst)   ;????????
  835.                "\\).*"
  836.                receive-divider-regexp))
  837.      (any-divider-regexp (concat "\\("
  838.                      receive-divider-regexp
  839.                      "\\|"
  840.                      zephyr-send-divider-regexp
  841.                      "\\)")))
  842.     (while (and (not (eobp))
  843.         (re-search-forward kill nil t))
  844.       (beginning-of-line 1)
  845.       (let ((p (point))
  846.         (found (re-search-forward any-divider-regexp nil t 2)))
  847.     (beginning-of-line 1)
  848.     (if found 
  849.         (delete-region p (point))
  850.       (end-of-line 1))))))
  851.  
  852. (defvar zephyr-log-buffer nil)
  853.  
  854. (defun zephyr-mode ()
  855.  
  856.   "major mode for sending and receiving zephyr-grams.  use
  857. zephyr-send-and-compose [\\[zephyr-send-and-compose]] to send
  858. messages.  instances are specified by enclosing their names in
  859. parentheses.  multiple destinations are seperated by whitespace or
  860. commas.  to change the destination just edit it, or use zephyr-compose
  861. [\\[zephyr-compose]].  if you want to send an instance to just one
  862. person, use \"user(instance)\".
  863.  
  864. in the composition buffer, the destinations for the current message
  865. appear to the left of \"<<< \".  when you send the zgram, everything
  866. between point and \"<<< \" will be transmitted.  at any time, you can
  867. edit the current destinations, or go back to previous messages and
  868. edit/send them.
  869.  
  870. when a message arrives, a beep will sound, unless message has arrived
  871. in the previous 120 (the value of zephyr-lazy-beep-time, really)
  872. seconds.  for more elaborate notification, use zephyr-notify-hook.
  873.  
  874. the output of the receiver process is kept in *log-zephyr* buffer.  the
  875. zephyr-log-buffer-limit and zephyr-buffer-limit variables control how
  876. much text is saved in the buffers.  additional text is discarded.
  877.  
  878.  
  879. \\{zephyr-mode-map}
  880.  
  881. this mode is highly customizable, there are many hooks and variables
  882. you can use to change how it behaves.  here's some of what you can do:
  883.  
  884.   visibly time-stamp incoming messages (see zephyr-hook-list)
  885.  
  886.   filter out particular instances/users (see
  887.      zephyr-instances-ignore-regexp)
  888.  
  889.   multiple receiving buffers with different hooks and regexps
  890.  
  891.   define aliases for sending to common groups of people (see
  892.      zephyr-aliases)"
  893.  
  894.   (interactive)
  895.   (kill-all-local-variables)
  896.  
  897.   (make-local-variable 'zephyr-senders-ignore-regexp)
  898.   (make-local-variable 'zephyr-instances-ignore-regexp)
  899.   (make-local-variable 'zephyr-senders-accept-regexp)
  900.   (make-local-variable 'zephyr-instances-accept-regexp)
  901.   (make-local-variable 'zephyr-previous-names)
  902.  
  903.   (set-syntax-table text-mode-syntax-table)
  904.   (use-local-map zephyr-mode-map)
  905.   (setq local-abbrev-table text-mode-abbrev-table)
  906.   (setq major-mode 'zephyr-mode)
  907.   (setq mode-name "Zephyr")
  908.   (setq zephyr-log-buffer (get-buffer-create "*log-zephyr*"))
  909.   (setq paragraph-start (concat "\\(" paragraph-start "\\|"
  910.                 "^.*" zephyr-send-divider-regexp "\\|"
  911.                 "^.*" zephyr-receive-divider "\\)"))
  912.   (if (or (not zephyr-log-buffer)
  913.       (not (eq 'run (process-status "zephyr-receive"))))
  914.       (zephyr-start-receiver))
  915.   (run-hooks 'text-mode-hook 'zephyr-mode-hook))
  916.  
  917. (setq zephyr-mode-map (make-sparse-keymap))
  918. (define-key zephyr-mode-map "\C-j" 'zephyr-send-and-compose)
  919. (define-key zephyr-mode-map "\C-c?" 'describe-mode)
  920. (define-key zephyr-mode-map "\C-c\C-s" 'zephyr-compose)
  921. (define-key zephyr-mode-map "\C-c\C-r" 'zephyr-restart-receiver)
  922. (define-key zephyr-mode-map "\C-c\C-c" 'zephyr-send-and-compose)
  923. (define-key zephyr-mode-map "\ep" 'zephyr-previous-destination)
  924. (define-key zephyr-mode-map "\en" 'zephyr-next-destination)
  925. (define-key zephyr-mode-map "\C-c\C-d" 'zephyr-delete-messages-from)
  926.  
  927. ;; stolen from fancy-xmouse.el by Benjamin C. Pierce (bcp@cs.cmu.edu)
  928. (defun time-difference-in-seconds (time1 time2)
  929.   (let* ((t1 (substring time1 11))
  930.       (t2 (substring time2 11))
  931.       (date1 (substring time1 0 11))
  932.       (date2 (substring time2 0 11))
  933.       (h1 (string-to-int (substring t1 0 2)))
  934.       (h2 (string-to-int (substring t2 0 2)))
  935.       (m1 (string-to-int (substring t1 3 5)))
  936.       (m2 (string-to-int (substring t2 3 5)))
  937.       (s1 (string-to-int (substring t1 6 8)))
  938.       (s2 (string-to-int (substring t2 6 8)))
  939.       (sec1 (+ (* 3600 h1) (* 60 m1) s1))
  940.       (sec2 (+ (* 3600 h2) (* 60 m2) s2)))
  941.     (+ (- sec2 sec1)
  942.        (if (string-equal date1 date2)
  943.         0
  944.      (* 3600 24) ; correction for passing midnight
  945.      ))))
  946.  
  947.