home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #16 / NN_1992_16.iso / spool / gnu / emacs / sources / 545 next >
Encoding:
Text File  |  1992-07-21  |  29.3 KB  |  895 lines

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