summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatteo F. Vescovi <mfv@debian.org>2016-11-06 14:36:10 +0100
committerMatteo F. Vescovi <mfv@debian.org>2016-11-06 14:36:10 +0100
commit484a3c53538e94f8d8c6a432b3c3174d98deb92f (patch)
tree4d1ea21a7698463dcfbdaf0461343c7ee25320fe
Import Upstream version 0.7
-rw-r--r--AUTHORS15
-rw-r--r--NEWS92
-rw-r--r--README104
-rw-r--r--filetransfer.txt55
-rw-r--r--hex-util.el75
-rw-r--r--jabber-activity.el373
-rw-r--r--jabber-ahc-presence.el105
-rw-r--r--jabber-ahc.el226
-rw-r--r--jabber-alert.el424
-rw-r--r--jabber-browse.el98
-rw-r--r--jabber-chat.el477
-rw-r--r--jabber-chatbuffer.el162
-rw-r--r--jabber-conn.el138
-rw-r--r--jabber-core.el511
-rw-r--r--jabber-disco.el202
-rw-r--r--jabber-events.el227
-rw-r--r--jabber-export.el234
-rw-r--r--jabber-feature-neg.el125
-rw-r--r--jabber-festival.el33
-rw-r--r--jabber-ft-client.el54
-rw-r--r--jabber-ft-server.el104
-rw-r--r--jabber-history.el244
-rw-r--r--jabber-iq.el177
-rw-r--r--jabber-keepalive.el95
-rw-r--r--jabber-keymap.el58
-rw-r--r--jabber-logon.el88
-rw-r--r--jabber-menu.el141
-rw-r--r--jabber-modeline.el95
-rw-r--r--jabber-muc.el745
-rw-r--r--jabber-presence.el346
-rw-r--r--jabber-ratpoison.el34
-rw-r--r--jabber-register.el143
-rw-r--r--jabber-roster.el456
-rw-r--r--jabber-sasl.el116
-rw-r--r--jabber-sawfish.el40
-rw-r--r--jabber-screen.el29
-rw-r--r--jabber-search.el115
-rw-r--r--jabber-si-client.el74
-rw-r--r--jabber-si-server.el100
-rw-r--r--jabber-socks5.el310
-rw-r--r--jabber-util.el444
-rw-r--r--jabber-vcard.el468
-rw-r--r--jabber-version.el72
-rw-r--r--jabber-watch.el72
-rw-r--r--jabber-widget.el322
-rw-r--r--jabber-xmessage.el30
-rw-r--r--jabber-xml.el206
-rw-r--r--jabber.el183
-rw-r--r--jabber.texi1863
-rw-r--r--sha1.el443
50 files changed, 11343 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..79bdf54
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,15 @@
+Developers:
+Tom Berger
+Magnus Henoch
+
+Contributors:
+Mathias Dahl
+Mario Domenech Goulart
+Nolan Eakins
+François Fleuret
+Justin Kirby
+Carl Henrik Lunde
+Andrey Slusar
+
+
+arch-tag: 15700144-3BD9-11D9-871C-000A95C2FCD0
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..41bddf7
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,92 @@
+-*- mode: outline -*-
+
+* New features in jabber.el 0.7
+
+** SSL connections possible
+See variable `jabber-connection-type'.
+
+** Chat buffers rewritten
+New modular design gives increased extensibility.
+*** Received URLs are displayed
+*** Long lines are filled
+See jabber-chat-fill-long-lines.
+*** Rare timestamps are printed by default
+See jabber-print-rare-time and jabber-rare-time-format.
+
+** MUC features
+*** Different default nicknames for different MUC rooms
+See jabber-muc-default-nicknames.
+*** Autojoin MUC rooms on connection
+See jabber-muc-autojoin.
+*** Change nickname
+Actually simply an alias from jabber-muc-nick to jabber-groupchat-join.
+*** Invitations
+Both sending and receiving invitiations is supported.
+*** Basic affiliation change support
+(Not finished)
+*** Private MUC messages
+*** Support for setting and displaying topic
+
+** Global key bindings
+Global keymap under C-x C-j.
+
+** Vcard viewer and editor
+
+** Roster export
+
+** Message events (JEP-0022)
+
+** Easy way to define external notifiers
+See define-jabber-alert. Alerts for Festival (speech synthesis),
+Sawfish, and xmessage added.
+
+** Activity mode improved
+Can now display count in frame title. Update hook added.
+
+** Roster display optimized
+
+** Optionally use per-contact history files
+
+** Jabber menu in menubar not enabled by default
+Call jabber-menu to have it there.
+
+** Flyspell in chat buffers
+Flyspell will only spell check what you're currently writing.
+
+** Different time formats for instant and delayed messages
+See `jabber-chat-time-format' and `jabber-chat-delayed-time-format'.
+You can see the complete timestamp in a tooltip by holding the mouse
+over the prompt.
+
+** Chat buffers in inactive windows are scrolled
+
+** Roster is sorted by name also
+
+* New features in jabber.el 0.6.1
+
+** Message history
+Set jabber-history-enabled to t to activate it.
+
+** Backlogs
+If you have history enabled, the last few messages are inserted when
+you open a new chat buffer.
+
+** Activity tracking on the mode line
+Activate it with M-x jabber-activity-mode.
+
+** Receive an alert when a specific person goes online
+Use it with M-x jabber-watch-add.
+
+** Support for /me in chats
+As in "/me laughs" etc.
+
+** Message alerts for current buffer can be disabled
+Set jabber-message-alert-same-buffer to nil to do that.
+
+** Basic moderation support in MUC
+
+** MUC alerts are separated from ordinary message alerts
+Customize jabber-alert-muc-hooks to get your desired behaviour.
+
+
+arch-tag: 1CE20E4E-3BD9-11D9-8D64-000A95C2FCD0
diff --git a/README b/README
new file mode 100644
index 0000000..5ef9faf
--- /dev/null
+++ b/README
@@ -0,0 +1,104 @@
+This is jabber.el 0.7, a Jabber client for Emacs. If you don't know
+what Jabber is, see http://www.jabber.org .
+
+Home page: http://emacs-jabber.sourceforge.net
+Project page: http://sourceforge.net/projects/emacs-jabber
+Wiki page: http://www.emacswiki.org/cgi-bin/wiki/JabberEl
+Mailing list: http://lists.sourceforge.net/lists/listinfo/emacs-jabber-general
+and: http://dir.gmane.org/gmane.emacs.jabber.general
+
+GNU Emacs
+=========
+
+jabber.el depends on GNU Emacs 21, in particular xml.el, and some
+files from Gnus 5.10. If you don't have Gnus 5.10 (M-x gnus-version
+will tell), you can download sha1.el and hex-util.el from Gnus CVS
+at http://quimby.gnus.org/cgi-bin/cvsweb.cgi/gnus/lisp/ . For your
+convenience, these files are included in the tarball.
+
+XEmacs
+======
+
+You need an XEmacs with Mule support, and recent versions of the gnus,
+net-utils and mule-ucs packages. jabber.el basically works on XEmacs,
+but some features are missing (in particular mouse support). Testing
+and patches are very welcome.
+
+SASL
+====
+jabber.el will use the SASL library of FLIM (Faithful Library about
+Internet Message; it is also included in newer versions of Gnus) if
+it's present. If not, it will fall back to JEP-0077 authentication.
+
+TLS/SSL
+=======
+To get an encrypted connection, you need either tls.el (from Gnus) or
+ssl.el. These are interfaces to GnuTLS and OpenSSL, respectively; use
+the appropriate one. Recent versions of tls.el support both programs,
+though. The version of ssl.el distributed with Gnus is outdated; use
+the one from W3 CVS instead:
+http://cvs.savannah.gnu.org/viewcvs/w3/lisp/ssl.el?root=w3
+
+To actually use encryption, customize the variables
+jabber-connection-type and jabber-connection-ssl-program.
+
+Note that only the connection from you to the server is encrypted;
+there is no guarantee of other connections being encrypted.
+
+StartTLS is not supported in this version of jabber.el.
+
+Installation
+============
+To install, put all .el files somewhere in your load-path (or have
+your load-path include the directory they're in) and put
+(require 'jabber) in your .emacs file. To install the Info
+documentation, copy jabber.info to /usr/local/info and run
+"install-info /usr/local/info/jabber.info".
+
+If you've been using a post-0.6 CVS version of jabber.el, you might
+need to remove some redundant hook functions. Make sure that
+jabber-alert-message-hooks doesn't contain jabber-message-history, and
+that jabber-alert-presence-hooks doesn't contain
+jabber-presence-watch.
+
+Usage
+=====
+To start using it, type M-x jabber-customize and set your username and
+server. Then, type C-x C-j C-c (or equivalently M-x jabber-connect)
+to connect (with prefix argument, register new account).
+
+Your roster is displayed in a buffer called *-jabber-*. To
+disconnect, type C-x C-j C-d or M-x jabber-disconnect.
+
+You may want to use the menu bar to execute Jabber commands. To
+enable the Jabber menu, type M-x jabber-menu.
+
+For a less terse description, read the enclosed manual.
+
+For bug reports, help requests and other feedback, use the trackers
+and forums at the project page mentioned above.
+
+Configuration
+=============
+All available configuration options are described in the manual. This
+section only serves to point out the most important ones.
+
+To change how you are notified about incoming events, type M-x
+customize-group RET jabber-alerts.
+
+To activate logging of all chats, set jabber-history-enabled to t. By
+default, history will be saved in ~/.jabber_global_message_log; make
+sure that this file has appropriate permissions. Type M-x
+customize-group RET jabber-history for more options.
+
+By default, jabber.el will send a confirmation when messages sent to
+you are delivered and displayed, and also send "contact is typing"
+notifications. To change this, type M-x customize-group RET
+jabber-events, and set the three jabber-events-confirm-* variables to
+nil.
+
+File transfer
+=============
+This release of jabber.el contains experimental support for file
+transfer. It is not enabled by default. See the file
+filetransfer.txt for details.
diff --git a/filetransfer.txt b/filetransfer.txt
new file mode 100644
index 0000000..756f7e3
--- /dev/null
+++ b/filetransfer.txt
@@ -0,0 +1,55 @@
+-*- outline -*-
+* File transfer
+
+This release of jabber.el contains some support for file transfer.
+Both sending and receiving files are supported. Since this feature
+needs more testing, it is not enabled by default. To enable it, add
+
+(require 'jabber-ft-server)
+(require 'jabber-ft-client)
+(require 'jabber-socks5)
+
+to your .emacs file. Please share your experiences - does it work for
+you? Can you suggest any improvements?
+
+** Sending files
+
+Sending files over Jabber normally requires the ability to listen on a
+network port. As of Emacs 21.3 and XEmacs 21.4, elisp programs can't
+do this, so you have to specify a JEP-0065 proxy. The variable
+jabber-socks5-proxies is a list of proxies to use. "proxy.jabber.org"
+and "proxy65.jabber.ccc.de" are the only proxies I know of.
+
+After you have specified one or more proxies, jabber.el needs to know
+their network addresses. Type M-x jabber-socks5-query-all-proxies,
+and watch the progress in the echo area. Note that you have to be
+connected when you do this, and that you have to do this every
+session.
+
+To send a file, type M-x jabber-ft-send. You will be asked for which
+file to send, and whom to send it to. You have to specify a complete
+JID with resource, such as user@domain/resource - only user@domain
+will not work. To see the resources of your contacts, set
+jabber-show-resources to t and type M-x jabber-display-roster.
+
+While the file is being sent, your Emacs will be locked up and you
+can't do anything else. Hopefully, this will be fixed some time.
+
+** Receiving files
+
+When someone tries to send a file to you, you will get a message
+either in the echo area or in a dialog box, asking you to confirm.
+You will also be asked for where to save the file.
+
+Receiving a file should not cause any interruption to your work. If
+it does, please tell.
+
+** Protocol details
+
+See JEPs 95, 96 and 65.
+
+SOCKS5 (JEP-0065) is the only stream method currently supported by
+jabber.el, in conflict with JEP-0096, which requires that In-Band
+Bytestreams be supported as well.
+
+Range requests are not supported, neither in sending nor in receiving.
diff --git a/hex-util.el b/hex-util.el
new file mode 100644
index 0000000..1cc67c2
--- /dev/null
+++ b/hex-util.el
@@ -0,0 +1,75 @@
+;;; hex-util.el --- Functions to encode/decode hexadecimal string.
+
+;; Copyright (C) 1999, 2001, 2002, 2003, 2004,
+;; 2005 Free Software Foundation, Inc.
+
+;; Author: Shuhei KOBAYASHI <shuhei@aqua.ocn.ne.jp>
+;; Keywords: data
+
+;; This file is part of FLIM (Faithful Library about Internet Message).
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(eval-when-compile
+ (defmacro hex-char-to-num (chr)
+ (` (let ((chr (, chr)))
+ (cond
+ ((and (<= ?a chr)(<= chr ?f)) (+ (- chr ?a) 10))
+ ((and (<= ?A chr)(<= chr ?F)) (+ (- chr ?A) 10))
+ ((and (<= ?0 chr)(<= chr ?9)) (- chr ?0))
+ (t (error "Invalid hexadecimal digit `%c'" chr))))))
+ (defmacro num-to-hex-char (num)
+ (` (aref "0123456789abcdef" (, num)))))
+
+(defun decode-hex-string (string)
+ "Decode hexadecimal STRING to octet string."
+ (let* ((len (length string))
+ (dst (make-string (/ len 2) 0))
+ (idx 0)(pos 0))
+ (while (< pos len)
+;;; logior and lsh are not byte-coded.
+;;; (aset dst idx (logior (lsh (hex-char-to-num (aref string pos)) 4)
+;;; (hex-char-to-num (aref string (1+ pos)))))
+ (aset dst idx (+ (* (hex-char-to-num (aref string pos)) 16)
+ (hex-char-to-num (aref string (1+ pos)))))
+ (setq idx (1+ idx)
+ pos (+ 2 pos)))
+ dst))
+
+(defun encode-hex-string (string)
+ "Encode octet STRING to hexadecimal string."
+ (let* ((len (length string))
+ (dst (make-string (* len 2) 0))
+ (idx 0)(pos 0))
+ (while (< pos len)
+;;; logand and lsh are not byte-coded.
+;;; (aset dst idx (num-to-hex-char (logand (lsh (aref string pos) -4) 15)))
+ (aset dst idx (num-to-hex-char (/ (aref string pos) 16)))
+ (setq idx (1+ idx))
+;;; (aset dst idx (num-to-hex-char (logand (aref string pos) 15)))
+ (aset dst idx (num-to-hex-char (% (aref string pos) 16)))
+ (setq idx (1+ idx)
+ pos (1+ pos)))
+ dst))
+
+(provide 'hex-util)
+
+;;; arch-tag: fe8aaa79-6c86-400e-813f-5a8cc4cb3859
+;;; hex-util.el ends here
diff --git a/jabber-activity.el b/jabber-activity.el
new file mode 100644
index 0000000..d9cc861
--- /dev/null
+++ b/jabber-activity.el
@@ -0,0 +1,373 @@
+;;; jabber-activity.el --- show jabber activity in the mode line
+
+;; Copyright (C) 2004 Carl Henrik Lunde - <chlunde+jabber+@ping.uio.no>
+
+;; This file is a part of jabber.el
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; Allows tracking messages from buddies using the global mode line
+;; See (info "(jabber)Tracking activity")
+
+;;; TODO:
+
+;; - Make it possible to enable this mode using M-x customize
+;; - When Emacs is on another desktop, (get-buffer-window buf 'visible)
+;; returns nil. We need to know when the user selects the frame again
+;; so we can remove the string from the mode line. (Or just run
+;; jabber-activity-clean often).
+;; - jabber-activity-switch-to needs a keybinding. In which map?
+;; - Is there any need for having defcustom jabber-activity-make-string?
+;; - When there's activity in a buffer it would be nice with a hook which
+;; does the opposite of bury-buffer, so switch-to-buffer will show that
+;; buffer first.
+
+;;; Code:
+
+(require 'jabber-core)
+(require 'jabber-alert)
+(require 'jabber-util)
+(require 'cl)
+
+(defgroup jabber-activity nil
+ "activity tracking options"
+ :group 'jabber)
+
+(defcustom jabber-activity-make-string 'jabber-activity-make-string-default
+ "Function to call, for making the string to put in the mode
+line. The default function returns the nick of the user."
+ :set #'(lambda (var val)
+ (custom-set-default var val)
+ (when (fboundp 'jabber-activity-make-name-alist)
+ (jabber-activity-make-name-alist)
+ (jabber-activity-mode-line-update)))
+ :type 'function
+ :group 'jabber-activity)
+
+(defcustom jabber-activity-shorten-minimum 1
+ "All strings returned by `jabber-activity-make-strings-shorten' will be
+at least this long, when possible."
+ :group 'jabber-activity
+ :type 'number)
+
+(defcustom jabber-activity-make-strings 'jabber-activity-make-strings-default
+ "Function which should return an alist of JID -> string when given a list of
+JIDs."
+ :set #'(lambda (var val)
+ (custom-set-default var val)
+ (when (fboundp 'jabber-activity-make-name-alist)
+ (jabber-activity-make-name-alist)
+ (jabber-activity-mode-line-update)))
+ :type '(choice (function-item :tag "Keep strings"
+ :value jabber-activity-make-strings-default)
+ (function-item :tag "Shorten strings"
+ :value jabber-activity-make-strings-shorten)
+ (function :tag "Other function"))
+ :group 'jabber-activity)
+
+(defcustom jabber-activity-count-in-title nil
+ "If non-nil, display number of active JIDs in frame title."
+ :type 'boolean
+ :group 'jabber-activity
+ :set #'(lambda (var val)
+ (custom-set-default var val)
+ (when (bound-and-true-p jabber-activity-mode)
+ (jabber-activity-mode -1)
+ (jabber-activity-mode 1))))
+
+(defcustom jabber-activity-count-in-title-format
+ '(jabber-activity-jids ("[" jabber-activity-count-string "] "))
+ "Format string used for displaying activity in frame titles.
+Same syntax as `mode-line-format'."
+ :type 'sexp
+ :group 'jabber-activity
+ :set #'(lambda (var val)
+ (if (not (bound-and-true-p jabber-activity-mode))
+ (custom-set-default var val)
+ (jabber-activity-mode -1)
+ (custom-set-default var val)
+ (jabber-activity-mode 1))))
+
+(defcustom jabber-activity-show-p 'jabber-activity-show-p-default
+ "Predicate function to call to check if the given JID should be
+shown in the mode line or not."
+ :type 'function
+ :group 'jabber-activity)
+
+(defcustom jabber-activity-query-unread t
+ "Query the user as to whether killing Emacs should be cancelled when
+there are unread messages which otherwise would be lost."
+ :type 'boolean
+ :group 'jabber-activity)
+
+(defface jabber-activity-face
+ '((t (:foreground "red" :weight bold)))
+ "The face for displaying jabber-activity-string in the mode line"
+ :group 'jabber-activity)
+
+(defvar jabber-activity-jids nil
+ "A list of JIDs which have caused activity")
+
+(defvar jabber-activity-name-alist nil
+ "Alist of mode line names for bare JIDs")
+
+(defvar jabber-activity-mode-string ""
+ "The mode string for jabber activity")
+
+(defvar jabber-activity-count-string "0"
+ "Number of active JIDs as a string.")
+
+(defvar jabber-activity-update-hook nil
+ "Hook called when `jabber-activity-jids' changes.
+It is called after `jabber-activity-mode-string' and
+`jabber-activity-count-string' are updated.")
+
+;; Protect this variable from being set in Local variables etc.
+(put 'jabber-activity-mode-string 'risky-local-variable t)
+(put 'jabber-activity-count-string 'risky-local-variable t)
+
+(defun jabber-activity-make-string-default (jid)
+ "Return the nick of the JID. If no nick is available, return
+the user name part of the JID. In private MUC conversations,
+return the user's nickname."
+ (if (jabber-muc-sender-p jid)
+ (jabber-jid-resource jid)
+ (let ((nick (jabber-jid-displayname jid))
+ (user (jabber-jid-user jid))
+ (username (jabber-jid-username jid)))
+ (if (and username (string= nick user))
+ username
+ nick))))
+
+(defun jabber-activity-make-strings-default (jids)
+ "Apply `jabber-activity-make-string' on JIDS"
+ (mapcar #'(lambda (jid) (cons jid (funcall jabber-activity-make-string jid)))
+ jids))
+
+(defun jabber-activity-common-prefix (s1 s2)
+ "Return length of common prefix string shared by S1 and S2"
+ (let ((len (min (length s1) (length s2))))
+ (or (dotimes (i len)
+ (when (not (eq (aref s1 i) (aref s2 i)))
+ (return i)))
+ ;; Substrings, equal, nil, or empty ("")
+ len)))
+
+(defun jabber-activity-make-strings-shorten (jids)
+ "Return an alist of JID -> names acquired by running
+`jabber-activity-make-string' on JIDS, and then shortening the names
+as much as possible such that all strings still are unique and at
+least `jabber-activity-shorten-minimum' long."
+ (let ((alist
+ (sort (mapcar
+ #'(lambda (x) (cons x (funcall jabber-activity-make-string x)))
+ jids)
+ #'(lambda (x y) (string-lessp (cdr x) (cdr y))))))
+ (loop for ((prev-jid . prev) (cur-jid . cur) (next-jid . next))
+ on (cons nil alist)
+ until (null cur)
+ collect
+ (cons
+ cur-jid
+ (substring
+ cur
+ 0 (min (length cur)
+ (max jabber-activity-shorten-minimum
+ (1+ (jabber-activity-common-prefix cur prev))
+ (1+ (jabber-activity-common-prefix cur next)))))))))
+
+(defun jabber-activity-find-buffer-name (jid)
+ "Find the name of the buffer that messages from JID would use."
+ (or (and (jabber-jid-resource jid)
+ (get-buffer (jabber-muc-private-get-buffer
+ (jabber-jid-user jid)
+ (jabber-jid-resource jid))))
+ (get-buffer (jabber-chat-get-buffer jid))
+ (get-buffer (jabber-muc-get-buffer jid))))
+
+(defun jabber-activity-show-p-default (jid)
+ "Returns t only if there is an invisible buffer for JID"
+ (let ((buffer (jabber-activity-find-buffer-name jid)))
+ (and (buffer-live-p buffer)
+ (not (get-buffer-window buffer 'visible)))))
+
+(defun jabber-activity-make-name-alist ()
+ "Rebuild `jabber-activity-name-alist' based on currently known JIDs"
+ (let ((jids (or (mapcar #'car jabber-activity-name-alist)
+ (mapcar #'symbol-name *jabber-roster*))))
+ (setq jabber-activity-name-alist
+ (funcall jabber-activity-make-strings jids))))
+
+(defun jabber-activity-lookup-name (jid)
+ "Lookup name in `jabber-activity-name-alist', creates an entry
+if needed, and returns a (jid . string) pair suitable for the mode line"
+ (let ((elm (assoc jid jabber-activity-name-alist)))
+ (if elm
+ elm
+ (progn
+ ;; Remake alist with the new JID
+ (setq jabber-activity-name-alist
+ (funcall jabber-activity-make-strings
+ (cons jid (mapcar #'car jabber-activity-name-alist))))
+ (jabber-activity-lookup-name jid)))))
+
+(defun jabber-activity-mode-line-update ()
+ "Update the string shown in the mode line using `jabber-activity-make-string'
+on JIDs where `jabber-activity-show-p'"
+ (setq jabber-activity-mode-string
+ (if jabber-activity-jids
+ (mapconcat
+ (lambda (x)
+ (let ((jump-to-jid (car x)))
+ (jabber-propertize
+ (cdr x)
+ 'face 'jabber-activity-face
+ 'local-map (make-mode-line-mouse-map
+ 'mouse-1 `(lambda ()
+ (interactive)
+ (jabber-activity-switch-to
+ ,(car x))))
+ 'help-echo (concat "Jump to "
+ (jabber-jid-displayname (car x))
+ "'s buffer"))))
+ (mapcar #'jabber-activity-lookup-name
+ jabber-activity-jids)
+ ",")
+ ""))
+ (setq jabber-activity-count-string
+ (number-to-string (length jabber-activity-jids)))
+ (force-mode-line-update 'all)
+ (run-hooks 'jabber-activity-update-hook))
+
+;;; Hooks
+
+(defun jabber-activity-clean ()
+ "Remove JIDs where `jabber-activity-show-p' no longer is true"
+ (setq jabber-activity-jids (delete-if-not jabber-activity-show-p
+ jabber-activity-jids))
+ (jabber-activity-mode-line-update))
+
+(defun jabber-activity-add (from buffer text proposed-alert)
+ "Add a JID to mode line when `jabber-activity-show-p'"
+ ;; In case of private MUC message, we want to keep the full JID.
+ (let ((jid (if (jabber-muc-sender-p from)
+ from
+ (jabber-jid-user from))))
+ (when (funcall jabber-activity-show-p jid)
+ (add-to-list 'jabber-activity-jids jid)
+ (jabber-activity-mode-line-update))))
+
+(defun jabber-activity-add-muc (nick group buffer text proposed-alert)
+ "Add a JID to mode line when `jabber-activity-show-p'"
+ (when (funcall jabber-activity-show-p group)
+ (add-to-list 'jabber-activity-jids group)
+ (jabber-activity-mode-line-update)))
+
+(defun jabber-activity-kill-hook ()
+ "Query the user as to whether killing Emacs should be cancelled
+when there are unread messages which otherwise would be lost, if
+`jabber-activity-query-unread' is t"
+ (if (and jabber-activity-jids
+ jabber-activity-query-unread)
+ (yes-or-no-p
+ "You have unread Jabber messages, are you sure you want to quit?")
+ t))
+
+;;; Interactive functions
+
+(defun jabber-activity-switch-to (&optional jid-param)
+ "If JID-PARAM is provided, switch to that buffer. If JID-PARAM is nil and
+there has been activity in another buffer, switch to that buffer. If no such
+buffer exists, switch back to most recently used buffer."
+ (interactive)
+ (if (or jid-param jabber-activity-jids)
+ (let ((jid (or jid-param (car jabber-activity-jids))))
+ (switch-to-buffer (jabber-activity-find-buffer-name jid))
+ (jabber-activity-clean))
+ ;; Switch back to the buffer used last
+ (switch-to-buffer nil)))
+
+;;;###autoload
+(define-minor-mode jabber-activity-mode
+ "Toggle display of activity in hidden jabber buffers in the mode line.
+
+With a numeric arg, enable this display if arg is positive."
+ :global t
+ :group 'jabber-activity
+ :init-value t
+ (if jabber-activity-mode
+ (progn
+ ;; XEmacs compatibilty hack from erc-track
+ (if (featurep 'xemacs)
+ (defadvice switch-to-buffer (after jabber-activity-update (&rest args) activate)
+ (jabber-activity-clean))
+ (add-hook 'window-configuration-change-hook
+ 'jabber-activity-clean))
+ (add-hook 'jabber-message-hooks
+ 'jabber-activity-add)
+ (add-hook 'jabber-muc-hooks
+ 'jabber-activity-add-muc)
+ (add-hook 'jabber-post-connect-hook
+ 'jabber-activity-make-name-alist)
+ (add-to-list 'kill-emacs-query-functions
+ 'jabber-activity-kill-hook)
+ (add-to-list 'global-mode-string
+ '(t jabber-activity-mode-string))
+ (when jabber-activity-count-in-title
+ ;; Be careful not to override specific meanings of the
+ ;; existing title format. In particular, if the car is
+ ;; a symbol, we can't just add our stuff at the beginning.
+ ;; If the car is "", we should be safe.
+ (if (equal (car frame-title-format) "")
+ (add-to-list 'frame-title-format
+ jabber-activity-count-in-title-format)
+ (setq frame-title-format (list ""
+ jabber-activity-count-in-title-format
+ frame-title-format)))
+ (if (equal (car icon-title-format) "")
+ (add-to-list 'icon-title-format
+ jabber-activity-count-in-title-format)
+ (setq icon-title-format (list ""
+ jabber-activity-count-in-title-format
+ icon-title-format)))))
+ (progn
+ (if (featurep 'xemacs)
+ (ad-disable-advice 'switch-to-buffer 'after 'jabber-activity-update)
+ (remove-hook 'window-configuration-change-hook
+ 'jabber-activity-remove-visible))
+ (remove-hook 'jabber-message-hooks
+ 'jabber-activity-add)
+ (remove-hook 'jabber-muc-hooks
+ 'jabber-activity-add-muc)
+ (remove-hook 'jabber-post-connect-hook
+ 'jabber-activity-make-name-alist)
+ (setq global-mode-string (delete '(t jabber-activity-mode-string)
+ global-mode-string))
+ (setq frame-title-format
+ (delete jabber-activity-count-in-title-format
+ frame-title-format))
+ (setq icon-title-format
+ (delete jabber-activity-count-in-title-format
+ icon-title-format)))))
+
+;; XXX: define-minor-mode should probably do this for us, but it doesn't.
+(if jabber-activity-mode (jabber-activity-mode 1))
+
+(provide 'jabber-activity)
+
+;; arch-tag: 127D7E42-356B-11D9-BE1E-000A95C2FCD0
diff --git a/jabber-ahc-presence.el b/jabber-ahc-presence.el
new file mode 100644
index 0000000..8239e45
--- /dev/null
+++ b/jabber-ahc-presence.el
@@ -0,0 +1,105 @@
+;; jabber-ahc-presence.el - provide remote control of presence
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-ahc)
+
+(defconst jabber-ahc-presence-node "presence"
+ "Node used by jabber-ahc-presence")
+
+(jabber-ahc-add jabber-ahc-presence-node "Set presence" 'jabber-ahc-presence
+ 'jabber-my-jid-p)
+
+(defun jabber-ahc-presence (xml-data)
+ "Process presence change command."
+
+ (let* ((query (jabber-iq-query xml-data))
+ (sessionid (jabber-xml-get-attribute query 'sessionid))
+ (action (jabber-xml-get-attribute query 'action)))
+ ;; No session state is kept; instead, lack of session-id is used
+ ;; as indication of first command.
+ (cond
+ ;; command cancelled
+ ((string= action "cancel")
+ `(command ((xmlns . "http://jabber.org/protocol/commands")
+ (sessionid . ,sessionid)
+ (node . ,jabber-ahc-presence-node)
+ (status . "canceled"))))
+ ;; return form
+ ((null sessionid)
+ `(command ((xmlns . "http://jabber.org/protocol/commands")
+ (sessionid . "jabber-ahc-presence")
+ (node . ,jabber-ahc-presence-node)
+ (status . "executing"))
+ (x ((xmlns . "jabber:x:data")
+ (type . "form"))
+ (title nil ,(format "Set presence of %s@%s/%s" jabber-username jabber-server jabber-resource))
+ (instructions nil "Select new presence status.")
+ (field ((var . "show")
+ (label . "Show")
+ (type . "list-single"))
+ (value nil ,(if (string= *jabber-current-show* "")
+ "online"
+ *jabber-current-show*))
+ (option ((label . "Online")) (value nil "online"))
+ (option ((label . "Chatty")) (value nil "chat"))
+ (option ((label . "Away")) (value nil "away"))
+ (option ((label . "Extended away")) (value nil "xa"))
+ (option ((label . "Do not disturb")) (value nil "dnd")))
+ (field ((var . "status")
+ (label . "Status text")
+ (type . "text-single"))
+ (value nil ,*jabber-current-status*))
+ (field ((var . "priority")
+ (label . "Priority")
+ (type . "text-single"))
+ (value nil ,(int-to-string *jabber-current-priority*))))))
+ ;; process form
+ (t
+ (let* ((x (car (jabber-xml-get-children query 'x)))
+ ;; we assume that the first <x/> is the jabber:x:data one
+ (fields (jabber-xml-get-children x 'field))
+ (new-show *jabber-current-show*)
+ (new-status *jabber-current-status*)
+ (new-priority *jabber-current-priority*))
+ (dolist (field fields)
+ (let ((var (jabber-xml-get-attribute field 'var))
+ ;; notice that multi-value fields won't be handled properly
+ ;; by this
+ (value (car (jabber-xml-node-children (car (jabber-xml-get-children field 'value))))))
+ (cond
+ ((string= var "show")
+ (setq new-show (if (string= value "online")
+ ""
+ value)))
+ ((string= var "status")
+ (setq new-status value))
+ ((string= var "priority")
+ (setq new-priority (string-to-int value))))))
+ (jabber-send-presence new-show new-status new-priority))
+ `(command ((xmlns . "http://jabber.org/protocol/commands")
+ (sessionid . ,sessionid)
+ (node . ,jabber-ahc-presence-node)
+ (status . "completed"))
+ (note ((type . "info")) "Presence has been changed."))))))
+
+(provide 'jabber-ahc-presence)
+
+;;; arch-tag: 4b8cbbe7-00a9-4d42-a4ac-b824ab914fba
diff --git a/jabber-ahc.el b/jabber-ahc.el
new file mode 100644
index 0000000..b2501d2
--- /dev/null
+++ b/jabber-ahc.el
@@ -0,0 +1,226 @@
+;; jabber-ahc.el - Ad-Hoc Commands by JEP-0050
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-disco)
+(require 'jabber-widget)
+
+(defvar jabber-ahc-sessionid nil
+ "session id of Ad-Hoc Command session")
+
+(defvar jabber-ahc-node nil
+ "node to send commands to")
+
+(defvar jabber-ahc-commands nil
+ "Commands provided
+
+This is an alist, where the keys are node names as strings (which
+means that they must not conflict). The values are plists having
+following properties:
+
+acl - function taking JID as single argument, return non-nil for
+ access allowed. No function means open for everyone.
+name - name of command
+func - function receiving entire IQ stanza as single argument
+ and returning a <command/> node
+
+Use the function `jabber-ahc-add' to add a command to this list.")
+
+
+;;; SERVER
+(add-to-list 'jabber-disco-info-nodes
+ (list "http://jabber.org/protocol/commands"
+ '((identity ((category . "automation")
+ (type . "command-list")
+ (name . "Ad-Hoc Command list")))
+ (feature ((var . "http://jabber.org/protocol/commands")))
+ (feature ((var . "http://jabber.org/protocol/disco#items")))
+ (feature
+ ((var . "http://jabber.org/protocol/disco#info"))))))
+
+(defun jabber-ahc-add (node name func acl)
+ "Add a command to internal lists.
+NODE is the node name to be used. It must be unique.
+NAME is the natural-language name of the command.
+FUNC is a function taking the entire IQ stanza as single argument when
+this command is invoked, and returns a <command/> node.
+ACL is a function taking JID as single argument, returning non-nil for
+access allowed. nil means open for everyone."
+ (add-to-list 'jabber-ahc-commands (cons node (list 'name name
+ 'func func
+ 'acl acl)))
+ (add-to-list 'jabber-disco-info-nodes
+ (list node `((identity ((category . "automation")
+ (type . "command-node")
+ (name . ,name)))
+ (feature ((var . "http://jabber.org/protocol/commands")))
+ (feature ((var . "http://jabber.org/protocol/disco#info")))
+ (feature ((var . "jabber:x:data")))))))
+
+(add-to-list 'jabber-advertised-features "http://jabber.org/protocol/commands")
+(add-to-list 'jabber-disco-items-nodes
+ (list "http://jabber.org/protocol/commands" #'jabber-ahc-disco-items nil))
+(defun jabber-ahc-disco-items (xml-data)
+ "Return commands in response to disco#items request"
+ (let ((jid (jabber-xml-get-attribute xml-data 'from)))
+ (mapcar (function
+ (lambda (command)
+ (let ((node (car command))
+ (plist (cdr command)))
+ (let ((acl (plist-get plist 'acl))
+ (name (plist-get plist 'name))
+ (func (plist-get plist 'func)))
+ (when (or (not (functionp acl))
+ (funcall acl jid))
+ `(item ((name . ,name)
+ (jid . ,(format "%s@%s/%s" jabber-username jabber-server jabber-resource))
+ (node . ,node))))))))
+ jabber-ahc-commands)))
+
+(add-to-list 'jabber-iq-set-xmlns-alist
+ (cons "http://jabber.org/protocol/commands" 'jabber-ahc-process))
+(defun jabber-ahc-process (xml-data)
+
+ (let ((to (jabber-xml-get-attribute xml-data 'from))
+ (id (jabber-xml-get-attribute xml-data 'id))
+ (node (jabber-xml-get-attribute (jabber-iq-query xml-data) 'node)))
+ ;; find command
+ (let* ((plist (cdr (assoc node jabber-ahc-commands)))
+ (acl (plist-get plist 'acl))
+ (func (plist-get plist 'func)))
+ (if plist
+ ;; found
+ (if (or (not (functionp acl))
+ (funcall acl to))
+ ;; access control passed
+ (jabber-send-iq to "result"
+ (funcall func xml-data)
+ nil nil nil nil id)
+ ;; ...or failed
+ (jabber-signal-error "cancel" 'not-allowed))
+ ;; No such node
+ (jabber-signal-error "cancel" 'item-not-found)))))
+
+;;; CLIENT
+(add-to-list 'jabber-jid-service-menu
+ (cons "Request command list" 'jabber-ahc-get-list))
+(defun jabber-ahc-get-list (to)
+ "Request list of ad-hoc commands. (JEP-0050)"
+ (interactive (list (jabber-read-jid-completing "Request command list from: ")))
+ (jabber-get-disco-items to "http://jabber.org/protocol/commands"))
+
+(add-to-list 'jabber-jid-service-menu
+ (cons "Execute command" 'jabber-ahc-execute-command))
+(defun jabber-ahc-execute-command (to node)
+ "Execute ad-hoc command. (JEP-0050)"
+ (interactive (list (jabber-read-jid-completing "Execute command of: ")
+ (jabber-read-node "Node of command: ")))
+ (jabber-send-iq to
+ "set"
+ `(command ((xmlns . "http://jabber.org/protocol/commands")
+ (node . ,node)
+ (action . "execute")))
+ #'jabber-process-data #'jabber-ahc-display
+ #'jabber-process-data "Command execution failed"))
+
+(defun jabber-ahc-display (xml-data)
+ (let* ((from (jabber-xml-get-attribute xml-data 'from))
+ (query (jabber-iq-query xml-data))
+ (node (jabber-xml-get-attribute query 'node))
+ (notes (jabber-xml-get-children query 'note))
+ (sessionid (jabber-xml-get-attribute query 'sessionid))
+ (status (jabber-xml-get-attribute query 'status))
+ (actions (car (jabber-xml-get-children query 'actions)))
+ xdata
+ (inhibit-read-only t))
+
+ (make-local-variable 'jabber-ahc-sessionid)
+ (setq jabber-ahc-sessionid sessionid)
+ (make-local-variable 'jabber-ahc-node)
+ (setq jabber-ahc-node node)
+
+ (dolist (x (jabber-xml-get-children query 'x))
+ (when (string= (jabber-xml-get-attribute x 'xmlns) "jabber:x:data")
+ (setq xdata x)))
+
+ (cond
+ ((string= status "executing")
+ (insert "Executing command\n\n"))
+ ((string= status "completed")
+ (insert "Command completed\n\n"))
+ ((string= status "canceled")
+ (insert "Command canceled\n\n")))
+
+ (dolist (note notes)
+ (let ((note-type (jabber-xml-get-attribute note 'type)))
+ (cond
+ ((string= note-type "warn")
+ (insert "Warning: "))
+ ((string= note-type "error")
+ (insert "Error: ")))
+ (insert (car (jabber-xml-node-children note)) "\n")))
+ (insert "\n")
+
+ (when xdata
+ (jabber-init-widget-buffer from)
+
+ (let ((formtype (jabber-xml-get-attribute xdata 'type)))
+ (if (string= formtype "result")
+ (jabber-render-xdata-search-results xdata)
+ (jabber-render-xdata-form xdata)
+
+ (when (string= status "executing")
+ (let ((button-titles
+ (cond
+ ((null actions)
+ '(complete cancel))
+ (t
+ (let ((children (mapcar #'jabber-xml-node-name (jabber-xml-node-children actions)))
+ (default-action (jabber-xml-get-attribute actions 'execute)))
+ (if (or (null default-action) (memq (intern default-action) children))
+ children
+ (cons (intern default-action) children)))))))
+ (dolist (button-title button-titles)
+ (widget-create 'push-button :notify `(lambda (&rest ignore) (jabber-ahc-submit (quote ,button-title))) (symbol-name button-title))
+ (widget-insert "\t")))
+ (widget-insert "\n"))))
+
+ (widget-setup)
+ (widget-minor-mode 1))))
+
+(defun jabber-ahc-submit (action)
+ "Submit Ad-Hoc Command."
+
+ (jabber-send-iq jabber-submit-to
+ "set"
+ `(command ((xmlns . "http://jabber.org/protocol/commands")
+ (sessionid . ,jabber-ahc-sessionid)
+ (node . ,jabber-ahc-node)
+ (action . ,(symbol-name action)))
+ ,(if (and (not (eq action 'cancel))
+ (eq jabber-form-type 'xdata))
+ (jabber-parse-xdata-form)))
+
+ #'jabber-process-data #'jabber-ahc-display
+ #'jabber-process-data "Command execution failed"))
+
+(provide 'jabber-ahc)
+
+;;; arch-tag: c0d5ed8c-50cb-44e1-8e0f-4058b79ee353
diff --git a/jabber-alert.el b/jabber-alert.el
new file mode 100644
index 0000000..5709e14
--- /dev/null
+++ b/jabber-alert.el
@@ -0,0 +1,424 @@
+;; jabber-alert.el - alert hooks
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004, 2005 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-util)
+
+(require 'cl)
+
+(defgroup jabber-alerts nil "auditory and visual alerts for jabber events"
+ :group 'jabber)
+
+(defcustom jabber-alert-message-hooks '(jabber-message-echo
+ jabber-message-scroll)
+ "Hooks run when a new message arrives.
+
+Arguments are FROM, BUFFER, TEXT and PROPOSED-ALERT. FROM is the JID
+of the sender, BUFFER is the the buffer where the message can be read,
+and TEXT is the text of the message. PROPOSED-ALERT is the string
+returned by `jabber-alert-message-function' for these arguments, so that
+hooks do not have to call it themselves.
+
+This hook is meant for user customization of message alerts. For
+other uses, see `jabber-message-hooks'."
+ :type 'hook
+ :options '(jabber-message-beep
+ jabber-message-wave
+ jabber-message-echo
+ jabber-message-switch
+ jabber-message-display
+ jabber-message-scroll)
+ :group 'jabber-alerts)
+
+(defvar jabber-message-hooks nil
+ "Internal hooks run when a new message arrives.
+
+This hook works just like `jabber-alert-message-hooks', except that
+it's not meant to be customized by the user.")
+
+(defcustom jabber-alert-message-function
+ 'jabber-message-default-message
+ "Function for constructing message alert messages.
+
+Arguments are FROM, BUFFER, and TEXT. This function should return a
+string containing an appropriate text message, or nil if no message
+should be displayed.
+
+The provided hooks displaying a text message get it from this function,
+and show no message if it returns nil. Other hooks do what they do
+every time."
+ :type 'function
+ :group 'jabber-alerts)
+
+(defcustom jabber-alert-muc-hooks '(jabber-muc-echo jabber-muc-scroll)
+ "Hooks run when a new MUC message arrives.
+
+Arguments are NICK, GROUP, BUFFER, TEXT and PROPOSED-ALERT. NICK
+is the nickname of the sender. GROUP is the JID of the group.
+BUFFER is the the buffer where the message can be read, and TEXT
+is the text of the message. PROPOSED-ALERT is the string
+returned by `jabber-alert-muc-function' for these arguments,
+so that hooks do not have to call it themselves."
+ :type 'hook
+ :options '(jabber-muc-beep
+ jabber-muc-wave
+ jabber-muc-echo
+ jabber-muc-switch
+ jabber-muc-display
+ jabber-muc-scroll)
+ :group 'jabber-alerts)
+
+(defvar jabber-muc-hooks '()
+ "Internal hooks run when a new MUC message arrives.
+
+This hook works just like `jabber-alert-muc-hooks', except that
+it's not meant to be customized by the user.")
+
+(defcustom jabber-alert-muc-function
+ 'jabber-muc-default-message
+ "Function for constructing message alert messages.
+
+Arguments are NICK, GROUP, BUFFER, and TEXT. This function
+should return a string containing an appropriate text message, or
+nil if no message should be displayed.
+
+The provided hooks displaying a text message get it from this function,
+and show no message if it returns nil. Other hooks do what they do
+every time."
+ :type 'function
+ :group 'jabber-alerts)
+
+(defcustom jabber-alert-presence-hooks
+ '(jabber-presence-update-roster
+ jabber-presence-echo)
+ "Hooks run when a user's presence changes.
+
+Arguments are WHO, OLDSTATUS, NEWSTATUS, STATUSTEXT and
+PROPOSED-ALERT. WHO is a symbol whose text is the JID of the contact,
+and which has various interesting properties. OLDSTATUS is the old
+presence or nil if disconnected. NEWSTATUS is the new presence, or
+one of \"subscribe\", \"unsubscribe\", \"subscribed\" and
+\"unsubscribed\". PROPOSED-ALERT is the string returned by
+`jabber-alert-presence-message-function' for these arguments."
+ :type 'hook
+ :options '(jabber-presence-beep
+ jabber-presence-wave
+ jabber-presence-update-roster
+ jabber-presence-switch
+ jabber-presence-display
+ jabber-presence-echo)
+ :group 'jabber-alerts)
+
+(defvar jabber-presence-hooks '(jabber-presence-watch)
+ "Internal hooks run when a user's presence changes.
+
+This hook works just like `jabber-alert-presence-hooks', except that
+it's not meant to be customized by the user.")
+
+(defcustom jabber-alert-presence-message-function
+ 'jabber-presence-default-message
+ "Function for constructing presence alert messages.
+
+Arguments are WHO, OLDSTATUS, NEWSTATUS and STATUSTEXT. See
+`jabber-alert-presence-hooks' for documentation. This function
+should return a string containing an appropriate text message, or nil
+if no message should be displayed.
+
+The provided hooks displaying a text message get it from this function.
+All hooks refrain from action if this function returns nil."
+ :type 'function
+ :group 'jabber-alerts)
+
+(defcustom jabber-alert-info-message-hooks '(jabber-info-display jabber-info-echo)
+ "Hooks run when an info request is completed.
+
+First argument is WHAT, a symbol telling the kind of info request completed.
+That might be 'roster, for requested roster updates, and 'browse, for
+browse requests. Second argument in BUFFER, a buffer containing the result.
+Third argument is PROPOSED-ALERT, containing the string returned by
+`jabber-alert-info-message-function' for these arguments."
+ :type 'hook
+ :options '(jabber-info-beep
+ jabber-info-wave
+ jabber-info-echo
+ jabber-info-switch
+ jabber-info-display)
+ :group 'jabber-alerts)
+
+(defvar jabber-info-message-hooks '()
+ "Internal hooks run when an info request is completed.
+
+This hook works just like `jabber-alert-info-message-hooks',
+except that it's not meant to be customized by the user.")
+
+(defcustom jabber-alert-info-message-function
+ 'jabber-info-default-message
+ "Function for constructing info alert messages.
+
+Arguments are WHAT, a symbol telling the kind of info request completed,
+and BUFFER, a buffer containing the result."
+ :type 'function
+ :group 'jabber-alerts)
+
+(defcustom jabber-info-message-alist
+ '((roster . "Roster display updated")
+ (browse . "Browse request completed"))
+ "Alist for info alert messages, used by `jabber-info-default-message'."
+ :type '(alist :key-type symbol :value-type string
+ :options (roster browse))
+ :group 'jabber-alerts)
+
+(defcustom jabber-alert-message-wave ""
+ "a sound file to play when a message arrived"
+ :type 'file
+ :group 'jabber-alerts)
+
+(defcustom jabber-alert-muc-wave ""
+ "a sound file to play when a MUC message arrived"
+ :type 'file
+ :group 'jabber-alerts)
+
+(defcustom jabber-alert-presence-wave ""
+ "a sound file to play when a presence arrived"
+ :type 'file
+ :group 'jabber-alerts)
+
+(defcustom jabber-alert-info-wave ""
+ "a sound file to play when an info query result arrived"
+ :type 'file
+ :group 'jabber-alerts)
+
+(defmacro define-jabber-alert (name docstring function)
+ "Define a new family of external alert hooks.
+Use this macro when your hooks do nothing except displaying a string
+in some new innovative way. You write a string display function, and
+this macro does all the boring and repetitive work.
+
+NAME is the name of the alert family. The resulting hooks will be
+called jabber-{message,muc,presence,info}-NAME.
+DOCSTRING is the docstring to use for those hooks.
+FUNCTION is a function that takes one argument, a string,
+and displays it in some meaningful way. It can be either a
+lambda form or a quoted function name.
+The created functions are inserted as options in Customize.
+
+Examples:
+\(define-jabber-alert foo \"Send foo alert\" 'foo-message)
+\(define-jabber-alert bar \"Send bar alert\"
+ (lambda (msg) (bar msg 42)))"
+ (let ((sn (symbol-name name)))
+ (let ((msg (intern (format "jabber-message-%s" sn)))
+ (muc (intern (format "jabber-muc-%s" sn)))
+ (pres (intern (format "jabber-presence-%s" sn)))
+ (info (intern (format "jabber-info-%s" sn))))
+ `(progn
+ (defun ,msg (from buffer text proposed-alert)
+ ,docstring
+ (when proposed-alert
+ (funcall ,function proposed-alert)))
+ (pushnew (quote ,msg) (get 'jabber-alert-message-hooks 'custom-options))
+ (defun ,muc (nick group buffer text proposed-alert)
+ ,docstring
+ (when proposed-alert
+ (funcall ,function proposed-alert)))
+ (pushnew (quote ,muc) (get 'jabber-alert-muc-hooks 'custom-options))
+ (defun ,pres (who oldstatus newstatus statustext proposed-alert)
+ ,docstring
+ (when proposed-alert
+ (funcall ,function proposed-alert)))
+ (pushnew (quote ,pres) (get 'jabber-alert-presence-hooks 'custom-options))
+ (defun ,info (infotype buffer proposed-alert)
+ ,docstring
+ (when proposed-alert
+ (funcall ,function proposed-alert)))
+ (pushnew (quote ,info) (get 'jabber-alert-info-message-hooks 'custom-options))))))
+
+;; Alert hooks
+(define-jabber-alert echo "Show a message in the echo area"
+ (lambda (msg) (message "%s" msg)))
+(define-jabber-alert beep "Beep on event"
+ (lambda (&rest ignore) (beep)))
+
+;; External notifiers
+(require 'jabber-screen)
+(require 'jabber-ratpoison)
+(require 'jabber-sawfish)
+(require 'jabber-festival)
+(require 'jabber-xmessage)
+
+;; Message alert hooks
+(defun jabber-message-default-message (from buffer text)
+ (when (or jabber-message-alert-same-buffer
+ (not (memq (selected-window) (get-buffer-window-list buffer))))
+ (if (jabber-muc-sender-p from)
+ (format "Private message from %s in %s"
+ (jabber-jid-resource from)
+ (jabber-jid-displayname (jabber-jid-user from)))
+ (format "Message from %s" (jabber-jid-displayname from)))))
+
+(defcustom jabber-message-alert-same-buffer t
+ "If nil, don't display message alerts for the current buffer."
+ :type 'boolean
+ :group 'jabber-alerts)
+
+(defun jabber-message-wave (from buffer text proposed-alert)
+ "Play the wave file specified in `jabber-alert-message-wave'"
+ (when proposed-alert
+ (jabber-play-sound-file jabber-alert-message-wave)))
+
+(defun jabber-message-display (from buffer text proposed-alert)
+ "Display the buffer where a new message has arrived."
+ (when proposed-alert
+ (display-buffer buffer)))
+
+(defun jabber-message-switch (from buffer text proposed-alert)
+ "Switch to the buffer where a new message has arrived."
+ (when proposed-alert
+ (switch-to-buffer buffer)))
+
+(defun jabber-message-scroll (from buffer text proposed-alert)
+ "Scroll all nonselected windows where the chat buffer is displayed."
+ ;; jabber-chat-buffer-display will DTRT with point in the buffer.
+ ;; But this change will not take effect in nonselected windows.
+ ;; Therefore we do that manually here.
+ ;;
+ ;; There are three cases:
+ ;; 1. The user started typing a message in this window. Point is
+ ;; greater than jabber-point-insert. In that case, we don't
+ ;; want to move point.
+ ;; 2. Point was at the end of the buffer, but no message was being
+ ;; typed. After displaying the message, point is now close to
+ ;; the end of the buffer. We advance it to the end.
+ ;; 3. The user was perusing history in this window. There is no
+ ;; simple way to distinguish this from 2, so the user loses.
+ (let ((windows (get-buffer-window-list buffer nil t))
+ (new-point-max (with-current-buffer buffer (point-max))))
+ (dolist (w windows)
+ (unless (eq w (selected-window))
+ (set-window-point w new-point-max)))))
+
+;; MUC alert hooks
+(defun jabber-muc-default-message (nick group buffer text)
+ (when (or jabber-message-alert-same-buffer
+ (not (memq (selected-window) (get-buffer-window-list buffer))))
+ (if nick
+ (format "Message from %s in %s" nick (jabber-jid-displayname
+ group))
+ (format "Message in %s" (jabber-jid-displayname group)))))
+
+(defun jabber-muc-wave (nick group buffer text proposed-alert)
+ "Play the wave file specified in `jabber-alert-muc-wave'"
+ (when proposed-alert
+ (jabber-play-sound-file jabber-alert-muc-wave)))
+
+(defun jabber-muc-display (nick group buffer text proposed-alert)
+ "Display the buffer where a new message has arrived."
+ (when proposed-alert
+ (display-buffer buffer)))
+
+(defun jabber-muc-switch (nick group buffer text proposed-alert)
+ "Switch to the buffer where a new message has arrived."
+ (when proposed-alert
+ (switch-to-buffer buffer)))
+
+(defun jabber-muc-scroll (nick group buffer text proposed-alert)
+ "Scroll buffer even if it is in an unselected window."
+ (jabber-message-scroll nil buffer nil nil))
+
+;; Presence alert hooks
+(defun jabber-presence-default-message (who oldstatus newstatus statustext)
+ "This function returns nil if OLDSTATUS and NEWSTATUS are equal, and in other
+cases a string of the form \"'name' (jid) is now NEWSTATUS (STATUSTEXT)\".
+
+This function is not called directly, but is the default for
+`jabber-alert-presence-message-function'."
+ (cond
+ ((equal oldstatus newstatus)
+ nil)
+ (t
+ (let ((formattedname
+ (if (> (length (get who 'name)) 0)
+ (get who 'name)
+ (symbol-name who)))
+ (formattedstatus
+ (or
+ (cdr (assoc newstatus
+ '(("subscribe" . " requests subscription to your presence")
+ ("subscribed" . " has granted presence subscription to you")
+ ("unsubscribe" . " no longer subscribes to your presence")
+ ("unsubscribed" . " cancels your presence subscription"))))
+ (concat " is now "
+ (or
+ (cdr (assoc newstatus jabber-presence-strings))
+ newstatus))))
+ (formattedtext
+ (if (> (length statustext) 0)
+ (concat " (" (jabber-unescape-xml statustext) ")")
+ "")))
+ (concat formattedname formattedstatus formattedtext)))))
+
+(defun jabber-presence-wave (who oldstatus newstatus statustext proposed-alert)
+ "Play the wave file specified in `jabber-alert-presence-wave'"
+ (if proposed-alert
+ (jabber-play-sound-file jabber-alert-presence-wave)))
+
+;; This is now defined in jabber-roster.el.
+;; (defun jabber-presence-update-roster (who oldstatus newstatus statustext proposed-alert)
+;; "Update the roster display by calling `jabber-display-roster'"
+;; (jabber-display-roster))
+
+(defun jabber-presence-display (who oldstatus newstatus statustext proposed-alert)
+ "Display the roster buffer"
+ (when proposed-alert
+ (display-buffer jabber-roster-buffer)))
+
+(defun jabber-presence-switch (who oldstatus newstatus statustext proposed-alert)
+ "Switch to the roster buffer"
+ (when proposed-alert
+ (switch-to-buffer jabber-roster-buffer)))
+
+;;; Info alert hooks
+
+(defun jabber-info-default-message (infotype buffer)
+ "Function for constructing info alert messages.
+
+The argument is INFOTYPE, a symbol telling the kind of info request completed.
+This function uses `jabber-info-message-alist' to find a message."
+ (concat (cdr (assq infotype jabber-info-message-alist))
+ " (buffer "(buffer-name buffer) ")"))
+
+(defun jabber-info-wave (infotype buffer proposed-alert)
+ "Play the wave file specified in `jabber-alert-info-wave'"
+ (if proposed-alert
+ (jabber-play-sound-file jabber-alert-info-wave)))
+
+(defun jabber-info-display (infotype buffer proposed-alert)
+ "Display buffer of completed request"
+ (when proposed-alert
+ (display-buffer buffer)))
+
+(defun jabber-info-switch (infotype buffer proposed-alert)
+ "Switch to buffer of completed request"
+ (when proposed-alert
+ (switch-to-buffer buffer)))
+
+(provide 'jabber-alert)
+
+;;; arch-tag: 725bd73e-c613-4fdc-a11d-3392a7598d4f
diff --git a/jabber-browse.el b/jabber-browse.el
new file mode 100644
index 0000000..b391a62
--- /dev/null
+++ b/jabber-browse.el
@@ -0,0 +1,98 @@
+;; jabber-browse.el - jabber browsing by JEP-0011
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-iq)
+(require 'jabber-xml)
+(require 'jabber-util)
+
+;; jabber.el can perform browse requests, but will not answer them.
+
+(add-to-list 'jabber-jid-info-menu
+ (cons "Send browse query" 'jabber-get-browse))
+(defun jabber-get-browse (to)
+ "send a browse infoquery request to someone"
+ (interactive (list (jabber-read-jid-completing "browse: ")))
+ (jabber-send-iq to
+ "get"
+ '(query ((xmlns . "jabber:iq:browse")))
+ #'jabber-process-data #'jabber-process-browse
+ #'jabber-process-data "Browse failed"))
+
+;; called from jabber-process-data
+(defun jabber-process-browse (xml-data)
+ "Handle results from jabber:iq:browse requests."
+ (dolist (item (jabber-xml-node-children xml-data))
+ (when (and (listp item)
+ (not (eq (jabber-xml-node-name item) 'ns)))
+ (let ((jid (jabber-xml-get-attribute item 'jid))
+ (beginning (point)))
+ (cond
+ ((or
+ (eq (jabber-xml-node-name item) 'user)
+ (string= (jabber-xml-get-attribute item 'category) "user"))
+ (insert (jabber-propertize "$ USER"
+ 'face 'jabber-title-medium)
+ "\n\n"))
+ ((or
+ (eq (jabber-xml-node-name item) 'service)
+ (string= (jabber-xml-get-attribute item 'category) "service"))
+ (insert (jabber-propertize "* SERVICE"
+ 'face 'jabber-title-medium)
+ "\n\n"))
+ ((or
+ (eq (jabber-xml-node-name item) 'conference)
+ (string= (jabber-xml-get-attribute item 'category) "conference"))
+ (insert (jabber-propertize "@ CONFERENCE"
+ 'face 'jabber-title-medium)
+ "\n\n"))
+ (t
+ ;; So far I've seen "server" and "directory", both in the node-name.
+ ;; Those are actually service disco categories, but jabberd 2 seems
+ ;; to use them for browse results as well. It's not right (as in
+ ;; JEP-0011), but it's reasonable.
+ (let ((category (jabber-xml-get-attribute item 'category)))
+ (if (= (length category) 0)
+ (setq category (jabber-xml-node-name item)))
+ (insert (jabber-propertize (format "! OTHER: %s" category)
+ 'face 'jabber-title-medium)
+ "\n\n"))))
+ (dolist (attr '((type . "Type:\t\t")
+ (jid . "JID:\t\t")
+ (name . "Name:\t\t")
+ (version . "Version:\t")))
+ (let ((data (jabber-xml-get-attribute item (car attr))))
+ (if (> (length data) 0)
+ (insert (cdr attr) (jabber-unescape-xml data) "\n"))))
+
+ (dolist (ns (jabber-xml-get-children item 'ns))
+ (if (stringp (car (jabber-xml-node-children ns)))
+ (insert "Namespace:\t" (car (jabber-xml-node-children ns)) "\n")))
+
+ (insert "\n")
+ (put-text-property beginning (point) 'jabber-jid jid)
+
+ ;; XXX: Is this kind of recursion really needed?
+ (if (listp (car (jabber-xml-node-children item)))
+ (jabber-process-browse item))))))
+
+(provide 'jabber-browse)
+
+;;; arch-tag: be01ab34-96eb-4fcb-aa35-a0d3e6c446c3
diff --git a/jabber-chat.el b/jabber-chat.el
new file mode 100644
index 0000000..67652ae
--- /dev/null
+++ b/jabber-chat.el
@@ -0,0 +1,477 @@
+;; jabber-chat.el - one-to-one chats
+
+;; Copyright (C) 2005 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-core)
+(require 'jabber-chatbuffer)
+(require 'jabber-history)
+
+(defgroup jabber-chat nil "chat display options"
+ :group 'jabber)
+
+(defcustom jabber-chat-buffer-format "*-jabber-chat-%n-*"
+ "The format specification for the name of chat buffers.
+
+These fields are available (all are about the person you are chatting
+with):
+
+%n Nickname, or JID if no nickname set
+%j Bare JID (without resource)
+%r Resource"
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-chat-header-line-format
+ '(" " (:eval (jabber-jid-displayname jabber-chatting-with))
+ "\t" (:eval (let ((buddy (jabber-jid-symbol jabber-chatting-with)))
+ (propertize
+ (or
+ (cdr (assoc (get buddy 'show) jabber-presence-strings))
+ (get buddy 'show))
+ 'face
+ (or (cdr (assoc (get buddy 'show) jabber-presence-faces))
+ 'jabber-roster-user-online))))
+ "\t" (:eval (get (jabber-jid-symbol jabber-chatting-with) 'status))
+ "\t" jabber-events-message) ;see jabber-events.el
+ "The specification for the header line of chat buffers.
+
+The format is that of `mode-line-format' and `header-line-format'."
+ :type 'sexp
+ :group 'jabber-chat)
+
+(defcustom jabber-chat-time-format "%H:%M"
+ "The format specification for instant messages in the chat buffer.
+See also `jabber-chat-delayed-time-format'.
+
+See `format-time-string' for valid values."
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-chat-delayed-time-format "%Y-%m-%d %H:%M"
+ "The format specification for delayed messages in the chat buffer.
+See also `jabber-chat-time-format'.
+
+See `format-time-string' for valid values."
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-print-rare-time t
+ "Non-nil means to print \"rare time\" indications in chat buffers.
+The default settings tell every new hour."
+ :type 'boolean
+ :group 'jabber-chat)
+
+(defcustom jabber-rare-time-format "%a %e %b %Y %H:00"
+ "The format specification for the rare time information.
+Rare time information will be printed whenever the current time,
+formatted according to this string, is different to the last
+rare time printed."
+ :type 'string
+ :group 'jabber-chat)
+
+(defface jabber-rare-time-face
+ '((t (:foreground "darkgreen" :underline t)))
+ "face for displaying the rare time info"
+ :group 'jabber-chat)
+
+(defvar jabber-rare-time ""
+ "Latest rare time printed")
+(make-variable-buffer-local 'jabber-rare-time)
+
+(defcustom jabber-chat-local-prompt-format "[%t] %n> "
+ "The format specification for lines you type in the chat buffer.
+
+These fields are available:
+
+%t Time, formatted according to `jabber-chat-time-format'
+ or `jabber-chat-delayed-time-format'
+%n Nickname (`jabber-nickname')
+%u Username
+%r Resource
+%j Bare JID (without resource)"
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-chat-foreign-prompt-format "[%t] %n> "
+ "The format specification for lines others type in the chat buffer.
+
+These fields are available:
+
+%t Time, formatted according to `jabber-chat-time-format'
+ or `jabber-chat-delayed-time-format'
+%n Nickname, or JID if no nickname set
+%u Username
+%r Resource
+%j Bare JID (without resource)"
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-chat-system-prompt-format "[%t] *** "
+ "The format specification for lines from the system or that are special in the chat buffer."
+ :type 'string
+ :group 'jabber-chat)
+
+(defface jabber-chat-prompt-local
+ '((t (:foreground "blue" :weight bold)))
+ "face for displaying the chat prompt for what you type in"
+ :group 'jabber-chat)
+
+(defface jabber-chat-prompt-foreign
+ '((t (:foreground "red" :weight bold)))
+ "face for displaying the chat prompt for what they send"
+ :group 'jabber-chat)
+
+(defface jabber-chat-prompt-system
+ '((t (:foreground "green" :weight bold)))
+ "face used for system and special messages"
+ :group 'jabber-chat)
+
+(defface jabber-chat-text-local nil
+ "Face used for text you write"
+ :group 'jabber-chat)
+
+(defface jabber-chat-text-foreign nil
+ "Face used for text others write"
+ :group 'jabber-chat)
+
+(defface jabber-chat-error
+ '((t (:foreground "red" :weight bold)))
+ "Face used for error messages"
+ :group 'jabber-chat)
+
+(defvar jabber-chatting-with nil
+ "JID of the person you are chatting with")
+
+(defvar jabber-chat-printers '(jabber-chat-print-subject
+ jabber-chat-print-body
+ jabber-chat-print-url
+ jabber-chat-goto-address)
+ "List of functions that may be able to print part of a message.
+Each function receives the entire <message/> stanza as argument.")
+
+(defvar jabber-body-printers '(jabber-chat-normal-body)
+ "List of functions that may be able to print a body for a message.
+Each function receives the entire <message/> stanza as argument, and
+should either output a representation of the body part of the message
+and return non-nil, or output nothing and return nil. These functions
+are called in order, until one of them returns non-nil.
+
+Add a function to the beginning of this list if the tag it handles
+replaces the contents of the <body/> tag.")
+
+(defvar jabber-chat-send-hooks nil
+ "List of functions called when a chat message is sent.
+The arguments are the text to send, and the id attribute of the
+message.
+
+The functions should return a list of XML nodes they want to be
+added to the outgoing message.")
+
+(defvar jabber-chat-earliest-backlog nil
+ "Float-time of earliest backlog entry inserted into buffer.
+nil if no backlog has been inserted.")
+
+(defun jabber-chat-get-buffer (chat-with)
+ "Return the chat buffer for chatting with CHAT-WITH (bare or full JID).
+Either a string or a buffer is returned, so use `get-buffer' or
+`get-buffer-create'."
+ (format-spec jabber-chat-buffer-format
+ (list
+ (cons ?n (jabber-jid-displayname chat-with))
+ (cons ?j (jabber-jid-user chat-with))
+ (cons ?r (jabber-jid-resource chat-with)))))
+
+(defun jabber-chat-create-buffer (chat-with)
+ "Prepare a buffer for chatting with CHAT-WITH.
+This function is idempotent."
+ (with-current-buffer (get-buffer-create (jabber-chat-get-buffer chat-with))
+ (if (not (eq major-mode 'jabber-chat-mode)) (jabber-chat-mode))
+ (make-local-variable 'jabber-chatting-with)
+ (setq jabber-chatting-with chat-with)
+ (setq jabber-send-function 'jabber-chat-send)
+ (setq header-line-format jabber-chat-header-line-format)
+
+ (make-local-variable 'jabber-chat-earliest-backlog)
+
+ ;; insert backlog
+ (when (zerop (buffer-size))
+ (let ((backlog-entries (jabber-history-backlog chat-with)))
+ (when backlog-entries
+ (setq jabber-chat-earliest-backlog
+ (jabber-float-time (jabber-parse-time
+ (aref (car backlog-entries) 0))))
+ (mapc 'jabber-chat-insert-backlog-entry backlog-entries))))
+
+ (current-buffer)))
+
+(defun jabber-chat-insert-backlog-entry (msg)
+ "Insert backlog entry MSG at point."
+ (if (string= (aref msg 1) "in")
+ (let ((fake-stanza `(message ((from . ,(aref msg 2)))
+ (body nil ,(aref msg 4))
+ (x ((xmlns . "jabber:x:delay")
+ (stamp . ,(jabber-encode-legacy-time (jabber-parse-time (aref msg 0)))))))))
+ (jabber-chat-buffer-display-at-point 'jabber-chat-print-prompt
+ fake-stanza
+ jabber-chat-printers
+ fake-stanza))
+ (jabber-chat-buffer-display-at-point 'jabber-chat-self-prompt
+ (jabber-parse-time (aref msg 0))
+ '(insert)
+ (jabber-propertize
+ (aref msg 4)
+ 'face 'jabber-chat-text-local))))
+
+(add-to-list 'jabber-jid-chat-menu
+ (cons "Display more context" 'jabber-chat-display-more-backlog))
+
+(defun jabber-chat-display-more-backlog (how-many)
+ (interactive "nHow many more messages? ")
+ (let* ((inhibit-read-only t)
+ (jabber-backlog-days nil)
+ (jabber-backlog-number how-many)
+ (backlog-entries (jabber-history-backlog
+ jabber-chatting-with jabber-chat-earliest-backlog)))
+ (when backlog-entries
+ (setq jabber-chat-earliest-backlog
+ (jabber-float-time (jabber-parse-time
+ (aref (car backlog-entries) 0))))
+ (save-excursion
+ (goto-char (point-min))
+ (mapc 'jabber-chat-insert-backlog-entry backlog-entries)))))
+
+(add-to-list 'jabber-message-chain 'jabber-process-chat)
+
+(defun jabber-process-chat (xml-data)
+ "If XML-DATA is a one-to-one chat message, handle it as such."
+ ;; XXX: there's more to being a chat message than not being MUC.
+ ;; Maybe make independent predicate.
+ (when (not (jabber-muc-message-p xml-data))
+ ;; Note that we handle private MUC messages here.
+ (let ((from (jabber-xml-get-attribute xml-data 'from))
+ (error-p (jabber-xml-get-children xml-data 'error))
+ (body-text (car (jabber-xml-node-children
+ (car (jabber-xml-get-children
+ xml-data 'body))))))
+ (with-current-buffer (if (jabber-muc-sender-p from)
+ (jabber-muc-private-create-buffer
+ (jabber-jid-user from)
+ (jabber-jid-resource from))
+ (jabber-chat-create-buffer from))
+ ;; Call alert hooks only when something is output
+ (when
+ (jabber-chat-buffer-display (if (jabber-muc-sender-p from)
+ 'jabber-muc-private-print-prompt
+ 'jabber-chat-print-prompt)
+ xml-data
+ (if error-p
+ '(jabber-chat-print-error)
+ jabber-chat-printers)
+ xml-data)
+
+ (dolist (hook '(jabber-message-hooks jabber-alert-message-hooks))
+ (run-hook-with-args hook
+ from (current-buffer) body-text
+ (funcall jabber-alert-message-function
+ from (current-buffer) body-text))))))))
+
+(defun jabber-chat-send (body)
+ "Send BODY, and display it in chat buffer."
+ (let* ((id (apply 'format "emacs-msg-%d.%d.%d" (current-time)))
+ (stanza-to-send `(message
+ ((to . ,jabber-chatting-with)
+ (type . "chat")
+ (id . ,id))
+ (body () ,(jabber-escape-xml body)))))
+ (dolist (hook jabber-chat-send-hooks)
+ (nconc stanza-to-send (funcall hook body id)))
+ (jabber-send-sexp stanza-to-send))
+
+ ;; Note that we pass a string, not an XML stanza,
+ ;; to the print functions.
+ (jabber-chat-buffer-display 'jabber-chat-self-prompt
+ nil
+ '(insert)
+ (jabber-propertize
+ body
+ 'face 'jabber-chat-text-local)))
+
+(defun jabber-maybe-print-rare-time (timestamp)
+ "Print rare time, if changed since last time printed."
+ (let ((new-time (format-time-string jabber-rare-time-format timestamp)))
+ (unless (string= new-time jabber-rare-time)
+ (setq jabber-rare-time new-time)
+ (when jabber-print-rare-time
+ (let ((inhibit-read-only t))
+ (goto-char jabber-point-insert)
+ (insert (jabber-propertize jabber-rare-time 'face 'jabber-rare-time-face) "\n")
+ (setq jabber-point-insert (point)))))))
+
+(defun jabber-chat-print-prompt (xml-data)
+ "Print prompt for received message in XML-DATA."
+ (let ((from (jabber-xml-get-attribute xml-data 'from))
+ (timestamp (car (delq nil (mapcar 'jabber-x-delay (jabber-xml-get-children xml-data 'x))))))
+ (jabber-maybe-print-rare-time timestamp)
+ (insert (jabber-propertize
+ (format-spec jabber-chat-foreign-prompt-format
+ (list
+ (cons ?t (format-time-string
+ (if timestamp
+ jabber-chat-delayed-time-format
+ jabber-chat-time-format)
+ timestamp))
+ (cons ?n (jabber-jid-displayname from))
+ (cons ?u (or (jabber-jid-username from) from))
+ (cons ?r (jabber-jid-resource from))
+ (cons ?j (jabber-jid-user from))))
+ 'face 'jabber-chat-prompt-foreign
+ 'help-echo
+ (concat (format-time-string "On %Y-%m-%d %H:%M:%S" timestamp) " from " from)))))
+
+(defun jabber-chat-self-prompt (timestamp)
+ "Print prompt for sent message.
+TIMESTAMP is the timestamp to print, or nil for now."
+ (jabber-maybe-print-rare-time timestamp)
+ (insert (jabber-propertize
+ (format-spec jabber-chat-local-prompt-format
+ (list
+ (cons ?t (format-time-string
+ (if timestamp
+ jabber-chat-delayed-time-format
+ jabber-chat-time-format)
+ timestamp))
+ (cons ?n jabber-nickname)
+ (cons ?u jabber-username)
+ (cons ?r jabber-resource)
+ (cons ?j (concat jabber-username "@" jabber-server))))
+ 'face 'jabber-chat-prompt-local
+ 'help-echo
+ (concat (format-time-string "On %Y-%m-%d %H:%M:%S" timestamp) " from you"))))
+
+(defun jabber-chat-print-error (xml-data)
+ "Print error in given <message/> in a readable way."
+ (let ((the-error (car (jabber-xml-get-children xml-data 'error))))
+ (insert
+ (jabber-propertize
+ (concat "Error: " (jabber-parse-error the-error))
+ 'face 'jabber-chat-error))))
+
+(defun jabber-chat-print-subject (xml-data)
+ "Print subject of given <message/>, if any."
+ (let ((subject (car
+ (jabber-xml-node-children
+ (car
+ (jabber-xml-get-children xml-data 'subject))))))
+ (when (not (zerop (length subject)))
+ (insert (jabber-propertize
+ "Subject: " 'face 'jabber-chat-prompt-system)
+ (jabber-propertize
+ subject
+ 'face 'jabber-chat-text-foreign)
+ "\n"))))
+
+(defun jabber-chat-print-body (xml-data)
+ (run-hook-with-args-until-success 'jabber-body-printers xml-data))
+
+(defun jabber-chat-normal-body (xml-data)
+ "Print body for received message in XML-DATA."
+ (let ((body (car
+ (jabber-xml-node-children
+ (car
+ (jabber-xml-get-children xml-data 'body))))))
+ (when body
+ (if (string-match "^/me \\(.*\\)$" body)
+ (let ((action (match-string 1 body))
+ (nick (if (jabber-muc-message-p xml-data)
+ (jabber-jid-resource (jabber-xml-get-attribute xml-data 'from))
+ (jabber-jid-displayname (jabber-xml-get-attribute xml-data 'from)))))
+ (insert (jabber-propertize
+ (concat nick
+ " "
+ action)
+ 'face 'jabber-chat-prompt-system)))
+ (insert (jabber-propertize body
+ 'face 'jabber-chat-text-foreign)))
+ t)))
+
+(defun jabber-chat-print-url (xml-data)
+ "Print URLs provided in jabber:x:oob namespace."
+ (dolist (x (jabber-xml-node-children xml-data))
+ (when (and (listp x) (eq (jabber-xml-node-name x) 'x)
+ (string= (jabber-xml-get-attribute x 'xmlns) "jabber:x:oob"))
+
+ (let ((url (car (jabber-xml-node-children
+ (car (jabber-xml-get-children x 'url)))))
+ (desc (car (jabber-xml-node-children
+ (car (jabber-xml-get-children x 'desc))))))
+ (insert (jabber-propertize
+ "URL: " 'face 'jabber-chat-prompt-system))
+ (insert (format "%s <%s>" desc url))
+ (insert "\n")))))
+
+(defun jabber-chat-goto-address (&rest ignore)
+ "Call `goto-address' on the newly written text."
+ (goto-address))
+
+(add-to-list 'jabber-jid-chat-menu
+ (cons "Send message" 'jabber-send-message))
+
+(defun jabber-send-message (to subject body type)
+ "send a message tag to the server"
+ (interactive (list (jabber-read-jid-completing "to: ")
+ (jabber-read-with-input-method "subject: ")
+ (jabber-read-with-input-method "body: ")
+ (read-string "type: ")))
+ (jabber-send-sexp `(message ((to . ,to)
+ ,(if (> (length type) 0)
+ `(type . ,type)))
+ ,(if (> (length subject) 0)
+ `(subject () ,(jabber-escape-xml subject)))
+ ,(if (> (length body) 0)
+ `(body () ,(jabber-escape-xml body)))))
+ (if (and jabber-history-enabled (not (string= type "groupchat")))
+ (jabber-history-log-message "out" nil to body (current-time))))
+
+(add-to-list 'jabber-jid-chat-menu
+ (cons "Start chat" 'jabber-chat-with))
+
+(defun jabber-chat-with (jid &optional other-window)
+ "Open an empty chat window for chatting with JID.
+With a prefix argument, open buffer in other window."
+ (interactive (list (jabber-read-jid-completing "chat with:")
+ current-prefix-arg))
+ (let ((buffer (jabber-chat-create-buffer jid)))
+ (if other-window
+ (switch-to-buffer-other-window buffer)
+ (switch-to-buffer buffer))))
+
+(defun jabber-chat-with-jid-at-point (&optional other-window)
+ "Start chat with JID at point.
+Signal an error if there is no JID at point.
+With a prefix argument, open buffer in other window."
+ (interactive "P")
+ (let ((jid-at-point (get-text-property (point)
+ 'jabber-jid)))
+ (if jid-at-point
+ (jabber-chat-with jid-at-point other-window)
+ (error "No contact at point"))))
+
+(provide 'jabber-chat)
+
+;; arch-tag: f423eb92-aa87-475b-b590-48c93ccba9be
diff --git a/jabber-chatbuffer.el b/jabber-chatbuffer.el
new file mode 100644
index 0000000..e729bb1
--- /dev/null
+++ b/jabber-chatbuffer.el
@@ -0,0 +1,162 @@
+;; jabber-chatbuffer.el - functions common to all chat buffers
+
+;; Copyright (C) 2005 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-keymap)
+
+(defvar jabber-point-insert nil
+ "Position where the message being composed starts")
+
+(defvar jabber-send-function nil
+ "Function for sending a message from a chat buffer.")
+
+(defvar jabber-chat-mode-hook nil
+ "Hook called at the end of `jabber-chat-mode'.
+Note that functions in this hook have no way of knowing
+what kind of chat buffer is being created.")
+
+(defcustom jabber-chat-fill-long-lines t
+ "If non-nil, fill long lines in chat buffers.
+Lines are broken at word boundaries at the width of the
+window or at `fill-column', whichever is shorter."
+ :group 'jabber-chat
+ :type 'boolean)
+
+(defun jabber-chat-mode ()
+ "\\{jabber-chat-mode-map}"
+ (kill-all-local-variables)
+ ;; Make sure to set this variable somewhere
+ (make-local-variable 'jabber-send-function)
+
+ (make-local-variable 'scroll-conservatively)
+ (setq scroll-conservatively 5)
+
+ (make-local-variable 'jabber-point-insert)
+ (setq jabber-point-insert (point-min))
+
+ ;;(setq header-line-format jabber-chat-header-line-format)
+
+ (setq major-mode 'jabber-chat-mode
+ mode-name "jabber-chat")
+ (use-local-map jabber-chat-mode-map)
+
+ (if (fboundp 'run-mode-hooks)
+ (run-mode-hooks 'jabber-chat-mode-hook)
+ (run-hooks 'jabber-chat-mode-hook)))
+
+(put 'jabber-chat-mode 'mode-class 'special)
+
+;; Spell check only what you're currently writing
+(defun jabber-chat-mode-flyspell-verify ()
+ (>= (point) jabber-point-insert))
+(put 'jabber-chat-mode 'flyspell-mode-predicate
+ 'jabber-chat-mode-flyspell-verify)
+
+(defvar jabber-chat-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map jabber-common-keymap)
+ (define-key map "\r" 'jabber-chat-buffer-send)
+ map))
+
+(defun jabber-chat-buffer-send ()
+ (interactive)
+ (let ((body (delete-and-extract-region jabber-point-insert (point-max))))
+ ;; If user accidentally hits RET without writing anything,
+ ;; delete-and-extract-region returns "". In that case,
+ ;; no message should be sent.
+ (unless (zerop (length body))
+ (funcall jabber-send-function body))))
+
+(defun jabber-chat-buffer-display (prompt-function prompt-data output-functions output-data)
+ "Display a message in current buffer.
+PROMPT-FUNCTION is a function that prints the correct prompt at
+point. It is called with PROMPT-DATA as argument.
+OUTPUT-FUNCTIONS is a list of functions that may or may not print something
+at point. They are called in order with OUTPUT-DATA as argument.
+If the OUTPUT-FUNCTIONS produce any output, PROMPT-FUNCTION is called
+with point before that output. If there is no output, there is
+no prompt. Return non-nil if there is output.
+
+If point is at or after jabber-point-insert, it is advanced.
+If point is before jabber-point-insert, it is not moved."
+ (let ((at-insert-point (eq (point) jabber-point-insert))
+ outputp)
+ (save-excursion
+ (goto-char jabber-point-insert)
+ (setq outputp
+ (jabber-chat-buffer-display-at-point prompt-function prompt-data output-functions output-data))
+ (setq jabber-point-insert (point))
+ (set-text-properties jabber-point-insert (point-max) nil))
+
+ (when at-insert-point
+ (goto-char jabber-point-insert))
+ outputp))
+
+(defun jabber-chat-buffer-display-at-point (prompt-function prompt-data output-functions output-data)
+ "Display a message at point.
+Arguments are as to `jabber-chat-buffer-display'.
+Return non-nil if any data was inserted."
+ (let ((inhibit-read-only t)
+ (beg (point))
+ (point-insert (set-marker (make-marker) jabber-point-insert)))
+ (set-marker-insertion-type point-insert t)
+
+ (dolist (printer output-functions)
+ (funcall printer output-data)
+ (unless (bolp)
+ (insert "\n")))
+
+ (unless (eq (point) beg)
+ (let ((end (point-marker)))
+ (goto-char beg)
+ (funcall prompt-function prompt-data)
+ (goto-char end)
+ (put-text-property beg end 'read-only t)
+ (put-text-property beg end 'front-sticky t)
+ (put-text-property beg end 'rear-nonsticky t)
+ (when jabber-chat-fill-long-lines
+ (save-restriction
+ (narrow-to-region beg end)
+ (jabber-chat-buffer-fill-long-lines)))
+ ;; this is always non-nil, so we return that
+ (setq jabber-point-insert (marker-position point-insert))))))
+
+(defun jabber-chat-buffer-fill-long-lines ()
+ "Fill lines that are wider than the window width."
+ ;; This was mostly stolen from article-fill-long-lines
+ (interactive)
+ (save-excursion
+ (let ((inhibit-read-only t)
+ (width (window-width (get-buffer-window (current-buffer)))))
+ (save-restriction
+ (goto-char (point-min))
+ (let ((adaptive-fill-mode nil)) ;Why? -sm
+ (while (not (eobp))
+ (end-of-line)
+ (when (>= (current-column) (min fill-column width))
+ (narrow-to-region (min (1+ (point)) (point-max))
+ (point-at-bol))
+ (let ((goback (point-marker)))
+ (fill-paragraph nil)
+ (goto-char (marker-position goback)))
+ (widen))
+ (forward-line 1)))))))
+
+(provide 'jabber-chatbuffer)
+;; arch-tag: 917e5b60-5894-4c49-b3bc-12e1f97ffdc6
diff --git a/jabber-conn.el b/jabber-conn.el
new file mode 100644
index 0000000..f3488ef
--- /dev/null
+++ b/jabber-conn.el
@@ -0,0 +1,138 @@
+;; jabber-conn.el - Network transport functions
+
+;; Copyright (C) 2005 - Georg Lehner - jorge@magma.com.ni
+;; mostly inspired by Gnus.
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+;; A collection of functions, that hide the details of transmitting to
+;; and fro a Jabber Server
+
+(eval-when-compile (require 'cl))
+
+;; Try two different TLS/SSL libraries, but don't fail if none available.
+(or (ignore-errors (require 'tls))
+ (ignore-errors (require 'ssl)))
+
+;; TODO: Add custom flag, to not complain about plain-text passwords
+;; in encrypted connections
+;;
+
+;; This variable holds the connection, which is used for further
+;; input/output to the server
+(defvar *jabber-connection* nil
+ "the process that does the actual connection")
+
+(defgroup jabber-conn nil "Jabber Connection Settings"
+ :group 'jabber)
+
+(defcustom jabber-network-server nil
+ "hostname or IP address of server to connect to, if different from `jabber-server'."
+ :type '(radio (const :tag "Same as `jabber-server'" nil)
+ (string :tag "Hostname or IP address"))
+ :group 'jabber-conn)
+
+(defcustom jabber-port nil
+ "jabber port
+The default depends on the connection type: 5222 for ordinary connections
+and 5223 for SSL connections."
+ :type '(choice (const :tag "Default" nil)
+ (integer :tag "Port number"))
+ :group 'jabber-conn)
+
+(defcustom jabber-connection-type 'network
+ "Type of connection to the jabber server, ssl or network most likely."
+ :type '(radio (const :tag "Encrypted connection, SSL" ssl)
+ (const :tag "Standard TCP/IP connection" network))
+ :group 'jabber-conn)
+
+(defcustom jabber-connection-ssl-program nil
+ "Program used for SSL/TLS connections.
+nil means prefer gnutls but fall back to openssl.
+'gnutls' means use gnutls (through `open-tls-stream').
+'openssl means use openssl (through `open-ssl-stream')."
+ :type '(choice (const :tag "Prefer gnutls, fall back to openssl" nil)
+ (const :tag "Use gnutls" gnutls)
+ (const :tag "Use openssl" openssl))
+ :group 'jabber-conn)
+
+(defvar jabber-connect-methods
+ '((network jabber-network-connect jabber-network-send)
+ (ssl jabber-ssl-connect jabber-ssl-send))
+ "Alist of connection methods and functions.
+First item is the symbol naming the method.
+Second item is the connect function.
+Third item is the send function.")
+
+(defvar jabber-connect-function nil
+ "function that connects to the jabber server")
+
+(defvar jabber-conn-send-function nil
+ "function that sends a line to the server")
+
+(defun jabber-setup-connect-method ()
+ (let ((entry (assq jabber-connection-type jabber-connect-methods)))
+ (setq jabber-connect-function (nth 1 entry))
+ (setq jabber-conn-send-function (nth 2 entry))))
+
+;; Plain TCP/IP connection
+(defun jabber-network-connect ()
+ (let ((coding-system-for-read 'utf-8)
+ (coding-system-for-write 'utf-8))
+ (setq *jabber-connection*
+ (open-network-stream
+ "jabber"
+ jabber-process-buffer
+ (or jabber-network-server jabber-server)
+ (or jabber-port 5222)))))
+
+(defun jabber-network-send (string)
+ "Send a string via a plain TCP/IP connection to the Jabber Server."
+ (process-send-string *jabber-connection* string))
+
+;; SSL connection, we use openssl's s_client function for encryption
+;; of the link
+;; TODO: make this configurable
+(defun jabber-ssl-connect ()
+ "connect via OpenSSL or GnuTLS to a Jabber Server"
+ (let ((coding-system-for-read 'utf-8)
+ (coding-system-for-write 'utf-8)
+ (connect-function
+ (cond
+ ((and (memq jabber-connection-ssl-program '(nil gnutls))
+ (fboundp 'open-tls-stream))
+ 'open-tls-stream)
+ ((and (memq jabber-connection-ssl-program '(nil openssl))
+ (fboundp 'open-ssl-stream))
+ 'open-ssl-stream)
+ (t
+ (error "Neither TLS nor SSL connect functions available")))))
+ (setq *jabber-connection*
+ (funcall connect-function
+ "jabber"
+ jabber-process-buffer
+ (or jabber-network-server jabber-server)
+ (or jabber-port 5223)))))
+
+(defun jabber-ssl-send (string)
+ "Send a string via an SSL-encrypted connection to the Jabber Server,
+ it seems we need to send a linefeed afterwards"
+ (process-send-string *jabber-connection* string)
+ (process-send-string *jabber-connection* "\n"))
+
+(provide 'jabber-conn)
+;; arch-tag: f95ec240-8cd3-11d9-9dbf-000a95c2fcd0
diff --git a/jabber-core.el b/jabber-core.el
new file mode 100644
index 0000000..257cd14
--- /dev/null
+++ b/jabber-core.el
@@ -0,0 +1,511 @@
+;; jabber-core.el - core functions
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; SSL-Connection Parts:
+;; Copyright (C) 2005 - Georg Lehner - jorge@magma.com.ni
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(eval-when-compile (require 'cl))
+
+(require 'jabber-util)
+(require 'jabber-logon)
+(require 'jabber-conn)
+
+;; SASL depends on FLIM.
+(eval-and-compile
+ (condition-case nil
+ (require 'jabber-sasl)
+ (error nil)))
+
+(defvar *jabber-roster* nil
+ "the roster list")
+
+(defvar jabber-jid-obarray (make-vector 127 0)
+ "obarray for keeping JIDs")
+
+(defvar *jabber-connected* nil
+ "boolean - are we connected")
+
+(defvar *jabber-authenticated* nil
+ "boolean - are we authenticated")
+
+(defvar *jabber-disconnecting* nil
+ "boolean - are we in the process of disconnecting by free will")
+
+(defvar *xmlq* ""
+ "a string with all the incoming xml that is waiting to be parsed")
+
+(defvar jabber-register-p nil
+ "Register a new account in this session?")
+
+(defvar jabber-session-id nil
+ "id of the current session")
+
+(defvar jabber-stream-version nil
+ "Stream version indicated by server")
+
+(defvar jabber-register-p nil
+ "Is account registration occurring in this session?")
+
+(defvar jabber-call-on-connection nil
+ "Function to be called on connection.
+This is set by `jabber-connect' on each call, and later picked up in
+`jabber-filter'.")
+
+(defvar jabber-short-circuit-input nil
+ "Function that receives all stanzas, instead of the usual ones.
+Used for SASL authentication.")
+
+(defvar jabber-message-chain nil
+ "Incoming messages are sent to these functions, in order.")
+
+(defvar jabber-iq-chain nil
+ "Incoming infoqueries are sent to these functions, in order.")
+
+(defvar jabber-presence-chain nil
+ "Incoming presence notifications are sent to these functions, in order.")
+
+(defvar jabber-stream-error-chain '(jabber-process-stream-error)
+ "Stream errors are sent to these functions, in order")
+
+(defvar jabber-choked-count 0
+ "Number of successive times that the process buffer has been nonempty.")
+
+(defvar jabber-choked-timer nil)
+
+(defgroup jabber-core nil "customize core functionality"
+ :group 'jabber)
+
+(defcustom jabber-post-connect-hook '(jabber-send-default-presence
+ jabber-muc-autojoin)
+ "*Hooks run after successful connection and authentication."
+ :type 'hook
+ :group 'jabber-core)
+
+(defcustom jabber-pre-disconnect-hook nil
+ "*Hooks run just before voluntary disconnection
+This might be due to failed authentication. Check `*jabber-authenticated*'."
+ :type 'hook
+ :group 'jabber-core)
+
+(defcustom jabber-lost-connection-hook nil
+ "*Hooks run after involuntary disconnection"
+ :type 'hook
+ :group 'jabber-core)
+
+(defcustom jabber-post-disconnect-hook nil
+ "*Hooks run after disconnection"
+ :type 'hook
+ :group 'jabber-core)
+
+(defcustom jabber-roster-buffer "*-jabber-*"
+ "The name of the roster buffer"
+ :type 'string
+ :group 'jabber-core)
+
+(defvar jabber-process-buffer " *-jabber-process-*"
+ "The name of the process buffer")
+
+(defcustom jabber-use-sasl t
+ "If non-nil, use SASL if possible.
+SASL will still not be used if the library for it is missing or
+if the server doesn't support it.
+
+Disabling this shouldn't be necessary, but it may solve certain
+problems."
+ :type 'boolean
+ :group 'jabber-core)
+
+(defsubst jabber-have-sasl-p ()
+ "Return non-nil if SASL functions are available."
+ (fboundp 'jabber-sasl-start-auth))
+
+(defun jabber-connect (&optional registerp)
+ "connect to the jabber server and start a jabber xml stream
+With prefix argument, register a new account."
+ (interactive "P")
+ (if *jabber-connected*
+ (message "Already connected")
+ (setq *xmlq* "")
+ (setq *jabber-authenticated* nil)
+ (jabber-clear-roster)
+ (jabber-reset-choked)
+
+ ;; Call the function responsible for establishing a bidirectional
+ ;; data stream to the Jabber Server, *jabber-connection* is set
+ ;; afterwards.
+ (jabber-setup-connect-method)
+ (funcall jabber-connect-function)
+ (unless *jabber-connection*
+ (error "Connection failed"))
+
+ ;; TLS connections leave data in the process buffer, which
+ ;; the XML parser will choke on.
+ (with-current-buffer (process-buffer *jabber-connection*)
+ (erase-buffer))
+ (set-process-filter *jabber-connection* #'jabber-pre-filter)
+ (set-process-sentinel *jabber-connection* #'jabber-sentinel)
+
+ (setq jabber-short-circuit-input nil)
+ (setq jabber-register-p registerp)
+
+ (setq jabber-call-on-connection (if registerp
+ #'(lambda (stream-features) (jabber-get-register jabber-server))
+ #'jabber-auth-somehow))
+ (let ((stream-header (concat "<?xml version='1.0'?><stream:stream to='"
+ jabber-server
+ "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'"
+ ;; Not supporting SASL is not XMPP compliant,
+ ;; so don't pretend we are.
+ (if (and (jabber-have-sasl-p) jabber-use-sasl)
+ " version='1.0'"
+ "")
+ ">
+")))
+
+ (funcall jabber-conn-send-function stream-header)
+ (if jabber-debug-log-xml
+ (with-current-buffer (get-buffer-create "*-jabber-xml-log-*")
+ (save-excursion
+ (goto-char (point-max))
+ (insert (format "sending %S\n\n" stream-header)))))
+
+ (setq jabber-choked-timer
+ (run-with-timer 5 5 #'jabber-check-choked))
+
+ (accept-process-output *jabber-connection*))
+ ;; Next thing happening is the server sending its own <stream:stream> start tag.
+ ;; That is handled in jabber-filter.
+
+ (setq *jabber-connected* t)))
+
+(defun jabber-auth-somehow (stream-features)
+ "Start authentication with SASL if the server supports it,
+otherwise JEP-0077. The STREAM-FEATURES argument is the stream features
+tag, or nil if we're connecting to a pre-XMPP server."
+ (if (and stream-features
+ jabber-use-sasl
+ (jabber-have-sasl-p)
+ jabber-stream-version
+ (>= (string-to-number jabber-stream-version) 1.0))
+ (jabber-sasl-start-auth stream-features)
+ (jabber-get-auth jabber-server)))
+
+(defun jabber-disconnect ()
+ "disconnect from the jabber server and re-initialise the jabber package variables"
+ (interactive)
+ (unless *jabber-disconnecting* ; avoid reentry
+ (let ((*jabber-disconnecting* t))
+ (when (and *jabber-connection*
+ (memq (process-status *jabber-connection*) '(open run)))
+ (run-hooks 'jabber-pre-disconnect-hook)
+ (funcall jabber-conn-send-function "</stream:stream>")
+ ;; let the server close the stream
+ (accept-process-output *jabber-connection* 3)
+ ;; and do it ourselves as well, just to be sure
+ (delete-process *jabber-connection*))
+ (jabber-disconnected)
+ (if (interactive-p)
+ (message "Disconnected from Jabber server")))))
+
+(defun jabber-disconnected ()
+ "Re-initialise jabber package variables.
+Call this function after disconnection."
+ (when jabber-choked-timer
+ (jabber-cancel-timer jabber-choked-timer)
+ (setq jabber-choked-timer nil))
+
+ (when (get-buffer jabber-roster-buffer)
+ (with-current-buffer (get-buffer jabber-roster-buffer)
+ (let ((inhibit-read-only t))
+ (erase-buffer))))
+
+ (setq *jabber-connection* nil)
+ (jabber-clear-roster)
+ (setq *xmlq* "")
+ (setq *jabber-authenticated* nil)
+ (setq *jabber-connected* nil)
+ (setq *jabber-active-groupchats* nil)
+ (setq jabber-session-id nil)
+ (run-hooks 'jabber-post-disconnect-hook))
+
+(defun jabber-sentinel (process event)
+ "alert user about lost connection"
+ (unless (or *jabber-disconnecting* (not *jabber-connected*))
+ (beep)
+ (run-hooks 'jabber-lost-connection-hook)
+ (message "Jabber connection lost: `%s'" event)
+ ;; If there is data left (maybe a stream error) process it first
+ (with-current-buffer (process-buffer process)
+ (unless (zerop (buffer-size))
+ (jabber-filter process)))
+ (jabber-disconnected)))
+
+(defun jabber-pre-filter (process string)
+ (with-current-buffer (process-buffer process)
+ ;; Append new data
+ (goto-char (point-max))
+ (insert string)
+
+ (unless (boundp 'jabber-filtering)
+ (let (jabber-filtering)
+ (jabber-filter process)))))
+
+(defun jabber-filter (process)
+ "the filter function for the jabber process"
+ (with-current-buffer (process-buffer process)
+ ;; Start from the beginning
+ (goto-char (point-min))
+ (let (xml-data)
+ (loop
+ do
+ ;; Skip whitespace
+ (unless (zerop (skip-chars-forward " \t\r\n"))
+ (delete-region (point-min) (point)))
+ ;; Skip processing directive
+ (when (looking-at "<\\?xml[^?]*\\?>")
+ (delete-region (match-beginning 0) (match-end 0)))
+
+ ;; Stream end?
+ (when (looking-at "</stream:stream>")
+ (return (jabber-disconnect)))
+
+ ;; Stream header?
+ (when (looking-at "<stream:stream[^>]*>")
+ (let ((stream-header (match-string 0))
+ (ending-at (match-end 0)))
+ ;; These regexps extract attribute values from the stream
+ ;; header, taking into account that the quotes may be either
+ ;; single or double quotes.
+ (setq jabber-session-id
+ (and (or (string-match "id='\\([^']+\\)'" stream-header)
+ (string-match "id=\"\\([^\"]+\\)\"" stream-header))
+ (jabber-unescape-xml (match-string 1 stream-header))))
+ (setq jabber-stream-version
+ (and (or
+ (string-match "version='\\([0-9.]+\\)'" stream-header)
+ (string-match "version=\"\\([0-9.]+\\)\"" stream-header))
+ (match-string 1 stream-header)))
+ (if jabber-debug-log-xml
+ (with-current-buffer (get-buffer-create "*-jabber-xml-log-*")
+ (save-excursion
+ (goto-char (point-max))
+ (insert (format "receive %S\n\n" stream-header)))))
+
+ ;; If the server is XMPP compliant, i.e. there is a version attribute
+ ;; and it's >= 1.0, there will be a stream:features tag shortly,
+ ;; so just wait for that.
+
+ ;; the stream feature is only sent if the initiating entity has
+ ;; sent 1.0 in the stream header. if sasl is not supported then
+ ;; we don't send 1.0 in the header and therefore we shouldn't wait
+ ;; even if 1.0 is present in the receiving stream.
+ (unless (and jabber-stream-version
+ (>= (string-to-number jabber-stream-version) 1.0)
+ jabber-use-sasl
+ (jabber-have-sasl-p))
+ ;; Logon or register
+ (funcall jabber-call-on-connection nil))
+
+ (delete-region (point-min) ending-at)))
+
+ ;; Normal tag
+
+ ;; XXX: do these checks make sense? If so, reinstate them.
+ ;;(if (active-minibuffer-window)
+ ;; (run-with-idle-timer 0.01 nil #'jabber-filter process string)
+
+ ;; This check is needed for xml.el of Emacs 21, as it chokes on
+ ;; empty attribute values.
+ (save-excursion
+ (while (search-forward-regexp " \\w+=''" nil t)
+ (replace-match "")))
+
+ (setq xml-data (and (catch 'unfinished
+ (jabber-xml-skip-tag-forward)
+ (> (point) (point-min)))
+ (xml-parse-region (point-min) (point))))
+ (if xml-data
+ (jabber-reset-choked))
+
+ while xml-data
+ do
+ ;; If there's a problem with writing the XML log,
+ ;; make sure the stanza is delivered, at least.
+ (condition-case e
+ (if jabber-debug-log-xml
+ (with-current-buffer (get-buffer-create "*-jabber-xml-log-*")
+ (save-excursion
+ (goto-char (point-max))
+ (insert (format "receive %S\n\n" (car xml-data))))))
+ (error
+ (ding)
+ (message "Couldn't write XML log: %s" (error-message-string e))
+ (sit-for 2)))
+ (delete-region (point-min) (point))
+ ;; We explicitly don't catch errors in jabber-process-input,
+ ;; to facilitate debugging.
+ (jabber-process-input (car xml-data))))))
+
+(defun jabber-reset-choked ()
+ (setq jabber-choked-count 0))
+
+(defun jabber-check-choked ()
+ ;; "Choked" means that data is sitting in the process buffer
+ ;; without being parsed, despite several attempts.
+ (if (zerop (buffer-size (process-buffer *jabber-connection*)))
+ (jabber-reset-choked)
+ (incf jabber-choked-count)
+ (if (and (> jabber-choked-count 3)
+ ;; Now we're definitely choked. Take action.
+ ;; But ask user first.
+ (yes-or-no-p "jabber.el is severely confused. Bail out? "))
+ (run-with-idle-timer 0.1 nil 'jabber-choked-bail-out)
+ (jabber-reset-choked))))
+
+(defun jabber-choked-bail-out ()
+ ;; So here we are. Something in the process buffer prevents us
+ ;; from continuing normally. Let's die honorably by providing
+ ;; bug report material.
+ (with-current-buffer (generate-new-buffer "*jabber-bug*")
+ (insert "jabber.el couldn't cope with the data received from the server.
+This should never happen, but apparently it did.
+
+The information below will be helpful in tracking down and fixing
+the bug. You may want to edit out any sensitive information.
+
+Please go to
+http://sourceforge.net/tracker/?group_id=88346&atid=586350 and
+submit a bug report, including the information below.
+
+")
+ (goto-address)
+ (emacs-version t)
+ (insert "\n\nThe following couldn't be parsed:\n")
+ (insert-buffer-substring (process-buffer *jabber-connection*))
+ (switch-to-buffer (current-buffer)))
+ (jabber-disconnect))
+
+(defun jabber-process-input (xml-data)
+ "process an incoming parsed tag"
+ (let* ((tag (jabber-xml-node-name xml-data))
+ (functions (eval (cdr (assq tag '((iq . jabber-iq-chain)
+ (presence . jabber-presence-chain)
+ (message . jabber-message-chain)
+ (stream:error . jabber-stream-error-chain)))))))
+ ;; Special treatment of the stream:features tag. The first time we get it,
+ ;; it means that we should authenticate. The second time, we should
+ ;; establish a session. (The zeroth time it's STARTTLS, but that's not
+ ;; implemented yet.)
+ (if (eq tag 'stream:features)
+ (if *jabber-authenticated*
+ (jabber-bind-and-establish-session xml-data)
+ (funcall jabber-call-on-connection xml-data))
+ (if jabber-short-circuit-input
+ (funcall jabber-short-circuit-input xml-data)
+ (dolist (f functions)
+ (funcall f xml-data))))))
+
+(defun jabber-process-stream-error (xml-data)
+ "Process an incoming stream error."
+ (beep)
+ (run-hooks 'jabber-lost-connection-hook)
+ (message "Stream error, connection lost: %s" (jabber-parse-stream-error xml-data))
+ (jabber-disconnect))
+
+(defun jabber-bind-and-establish-session (xml-data)
+ ;; Now we have a stream:features tag. We expect it to contain bind and
+ ;; session tags. If it doesn't, the server we are connecting to is no
+ ;; IM server.
+ (unless (and (jabber-xml-get-children xml-data 'bind)
+ (jabber-xml-get-children xml-data 'session))
+ (jabber-disconnect)
+ (error "Server doesn't permit resource binding and session establishing"))
+
+ ;; So let's bind a resource. We can either pick a resource ourselves,
+ ;; or have the server pick one for us.
+ (jabber-send-iq nil "set"
+ `(bind ((xmlns . "urn:ietf:params:xml:ns:xmpp-bind"))
+ (resource () ,jabber-resource))
+ #'jabber-process-bind t
+ #'jabber-process-bind nil))
+
+(defun jabber-process-bind (xml-data successp)
+ (unless successp
+ (jabber-disconnect)
+ (error "Resource binding failed: %s"
+ (jabber-parse-error (car (jabber-xml-get-children xml-data 'error)))))
+
+ (let ((jid (car
+ (jabber-xml-node-children
+ (car
+ (jabber-xml-get-children
+ (jabber-iq-query xml-data) 'jid))))))
+ ;; Maybe this isn't the JID we asked for.
+ (setq jabber-username (jabber-jid-username jid))
+ (setq jabber-server (jabber-jid-server jid))
+ (setq jabber-resource (jabber-jid-resource jid)))
+
+ ;; Been there, done that. Time to establish a session.
+ (jabber-send-iq nil "set"
+ '(session ((xmlns . "urn:ietf:params:xml:ns:xmpp-session")))
+ #'jabber-process-session t
+ #'jabber-process-session nil))
+
+(defun jabber-process-session (xml-data successp)
+ (unless successp
+ (jabber-disconnect)
+ (error "Session establishing failed: %s"
+ (jabber-parse-error (car (jabber-xml-get-children xml-data 'error)))))
+
+ ;; Now, request roster.
+ (jabber-send-iq nil
+ "get"
+ '(query ((xmlns . "jabber:iq:roster")))
+ #'jabber-process-roster 'initial
+ #'jabber-report-success "Roster retrieval")
+
+ (run-hooks 'jabber-post-connect-hook))
+
+(defun jabber-clear-roster ()
+ "Clean up the roster."
+ ;; This is made complicated by the fact that the JIDs are symbols with properties.
+ (mapatoms #'(lambda (x)
+ (unintern x jabber-jid-obarray))
+ jabber-jid-obarray)
+ (setq *jabber-roster* nil))
+
+(defun jabber-send-sexp (sexp)
+ "send the xml corresponding to SEXP to the jabber server"
+ (condition-case e
+ (if jabber-debug-log-xml
+ (with-current-buffer (get-buffer-create "*-jabber-xml-log-*")
+ (save-excursion
+ (goto-char (point-max))
+ (insert (format "sending %S\n\n" sexp)))))
+ (error
+ (ding)
+ (message "Couldn't write XML log: %s" (error-message-string e))
+ (sit-for 2)))
+ (funcall jabber-conn-send-function (jabber-sexp2xml sexp)))
+
+(provide 'jabber-core)
+
+;;; arch-tag: 9d273ce6-c45a-447b-abf3-21d3ce73a51a
diff --git a/jabber-disco.el b/jabber-disco.el
new file mode 100644
index 0000000..226be6e
--- /dev/null
+++ b/jabber-disco.el
@@ -0,0 +1,202 @@
+;; jabber-disco.el - service discovery functions
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+;;; All the client part should be seriously rewritten, or at least
+;;; reconsidered. I'm imagining a separation between backend and
+;;; frontend, so that various functions can perform disco queries for
+;;; their own purposes, and maybe some caching with that.
+
+(require 'jabber-iq)
+(require 'jabber-xml)
+(require 'jabber-menu)
+
+;; Advertise your features here. Add the namespace to this list.
+(defvar jabber-advertised-features
+ (list "http://jabber.org/protocol/disco#info")
+ "Features advertised on service discovery requests")
+
+(defvar jabber-disco-items-nodes
+ (list
+ (list "" nil nil))
+ "Alist of node names and information about returning disco item data.
+Key is node name as a string, or \"\" for no node specified. Value is
+a list of two items.
+
+First item is data to return. If it is a function, that function is
+called and its return value is used; if it is a list, that list is
+used. The list should be the XML data to be returned inside the
+<query/> element, like this:
+
+((item ((name . \"Name of first item\")
+ (jid . \"first.item\")
+ (node . \"node\"))))
+
+Second item is access control function. That function is passed the
+JID, and returns non-nil if access is granted. If the second item is
+nil, access is always granted.")
+
+(defvar jabber-disco-info-nodes
+ (list
+ (list "" #'jabber-disco-return-client-info nil))
+ "Alist of node names and information returning disco info data.
+Key is node name as a string, or \"\" for no node specified. Value is
+a list of two items.
+
+First item is data to return. If it is a function, that function is
+called and its return value is used; if it is a list, that list is
+used. The list should be the XML data to be returned inside the
+<query/> element, like this:
+
+((identity ((category . \"client\")
+ (type . \"pc\")
+ (name . \"Jabber client\")))
+ (feature ((var . \"some-feature\"))))
+
+Second item is access control function. That function is passed the
+JID, and returns non-nil if access is granted. If the second item is
+nil, access is always granted.")
+
+(defun jabber-process-disco-info (xml-data)
+ "Handle results from info disco requests."
+
+ (let ((beginning (point)))
+ (dolist (x (jabber-xml-node-children (jabber-iq-query xml-data)))
+ (cond
+ ((eq (jabber-xml-node-name x) 'identity)
+ (let ((name (jabber-xml-get-attribute x 'name))
+ (category (jabber-xml-get-attribute x 'category))
+ (type (jabber-xml-get-attribute x 'type)))
+ (insert (jabber-propertize (if name
+ (jabber-unescape-xml name)
+ "Unnamed")
+ 'face 'jabber-title-medium)
+ "\n\nCategory:\t" category "\n")
+ (if type
+ (insert "Type:\t\t" type "\n"))
+ (insert "\n")))
+ ((eq (jabber-xml-node-name x) 'feature)
+ (let ((var (jabber-xml-get-attribute x 'var)))
+ (insert "Feature:\t" var "\n")))))
+ (put-text-property beginning (point) 'jabber-jid (jabber-xml-get-attribute xml-data 'from))))
+
+(defun jabber-process-disco-items (xml-data)
+ "Handle results from items disco requests."
+
+ (let ((items (jabber-xml-get-children (jabber-iq-query xml-data) 'item)))
+ (if items
+ (dolist (item items)
+ (let ((jid (jabber-xml-get-attribute item 'jid))
+ (name (jabber-xml-get-attribute item 'name))
+ (node (jabber-xml-get-attribute item 'node)))
+ (insert
+ (jabber-propertize
+ (concat
+ (jabber-propertize
+ (concat jid "\n" (if node (format "Node: %s\n" node)))
+ 'face 'jabber-title-medium)
+ (jabber-unescape-xml name) "\n\n")
+ 'jabber-jid jid
+ 'jabber-node node))))
+ (insert "No items found.\n"))))
+
+(add-to-list 'jabber-iq-get-xmlns-alist
+ (cons "http://jabber.org/protocol/disco#info" 'jabber-return-disco-info))
+(add-to-list 'jabber-iq-get-xmlns-alist
+ (cons "http://jabber.org/protocol/disco#items" 'jabber-return-disco-info))
+(defun jabber-return-disco-info (xml-data)
+ "Respond to a service discovery request.
+See JEP-0030."
+ (let* ((to (jabber-xml-get-attribute xml-data 'from))
+ (id (jabber-xml-get-attribute xml-data 'id))
+ (xmlns (jabber-iq-xmlns xml-data))
+ (which-alist (eval (cdr (assoc xmlns
+ (list
+ (cons "http://jabber.org/protocol/disco#info" 'jabber-disco-info-nodes)
+ (cons "http://jabber.org/protocol/disco#items" 'jabber-disco-items-nodes))))))
+ (node (or
+ (jabber-xml-get-attribute (jabber-iq-query xml-data) 'node)
+ ""))
+ (return-list (cdr (assoc node which-alist)))
+ (func (nth 0 return-list))
+ (access-control (nth 1 return-list)))
+ (if return-list
+ (if (and (functionp access-control)
+ (not (funcall access-control to)))
+ (jabber-signal-error "cancel" 'not-allowed)
+ ;; Access control passed
+ (let ((result (if (functionp func)
+ (funcall func xml-data)
+ func)))
+ (jabber-send-iq to "result"
+ `(query ((xmlns . ,xmlns))
+ ,@result)
+ nil nil nil nil id)))
+
+ ;; No such node
+ (jabber-signal-error "cancel" 'item-not-found))))
+
+(defun jabber-disco-return-client-info (xml-data)
+ `(
+ ;; If running under a window system, this is
+ ;; a GUI client. If not, it is a console client.
+ (identity ((category . "client")
+ (name . "Emacs Jabber client")
+ (type . ,(if (memq window-system
+ '(x w32 mac))
+ "pc"
+ "console"))))
+ ,@(mapcar
+ #'(lambda (featurename)
+ `(feature ((var . ,featurename))))
+ jabber-advertised-features)))
+
+(add-to-list 'jabber-jid-info-menu
+ (cons "Send items disco query" 'jabber-get-disco-items))
+(defun jabber-get-disco-items (to &optional node)
+ "Send a service discovery request for items"
+ (interactive (list (jabber-read-jid-completing "Send items disco request to: ")
+ (jabber-read-node "Node (or leave empty): ")))
+ (jabber-send-iq to
+ "get"
+ (list 'query (append (list (cons 'xmlns "http://jabber.org/protocol/disco#items"))
+ (if (> (length node) 0)
+ (list (cons 'node node)))))
+ #'jabber-process-data #'jabber-process-disco-items
+ #'jabber-process-data "Item discovery failed"))
+
+(add-to-list 'jabber-jid-info-menu
+ (cons "Send info disco query" 'jabber-get-disco-info))
+(defun jabber-get-disco-info (to &optional node)
+ "Send a service discovery request for info"
+ (interactive (list (jabber-read-jid-completing "Send info disco request to: ")
+ (jabber-read-node "Node (or leave empty): ")))
+ (jabber-send-iq to
+ "get"
+ (list 'query (append (list (cons 'xmlns "http://jabber.org/protocol/disco#info"))
+ (if (> (length node) 0)
+ (list (cons 'node node)))))
+ #'jabber-process-data #'jabber-process-disco-info
+ #'jabber-process-data "Info discovery failed"))
+
+(provide 'jabber-disco)
+
+;;; arch-tag: 71f5c76f-2956-4ed2-b871-9f5fe198092d
diff --git a/jabber-events.el b/jabber-events.el
new file mode 100644
index 0000000..b9fc556
--- /dev/null
+++ b/jabber-events.el
@@ -0,0 +1,227 @@
+;;; jabber-events.el --- Message events (JEP-0022) implementation
+
+;; Copyright (C) 2005 Magnus Henoch
+
+;; Author: Magnus Henoch <mange@freemail.hu>
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+(require 'cl)
+
+(defgroup jabber-events nil
+ "Message events and notifications."
+ :group 'jabber)
+
+;;; INCOMING
+;;; Code for requesting event notifications from others and handling
+;;; them.
+
+(defcustom jabber-events-request-these '(offline
+ delivered
+ displayed
+ composing)
+ "Request these kinds of event notifications from others."
+ :type '(set (const :tag "Delivered to offline storage" offline)
+ (const :tag "Delivered to user's client" delivered)
+ (const :tag "Displayed to user" displayed)
+ (const :tag "User is typing a reply" composing))
+ :group 'jabber-events)
+
+(defvar jabber-events-composing-p nil
+ "Is the other person composing a message?")
+(make-variable-buffer-local 'jabber-events-composing-p)
+
+(defvar jabber-events-arrived nil
+ "In what way has the message reached the recipient?
+Possible values are nil (no information available), offline
+\(queued for delivery when recipient is online), delivered
+\(message has reached the client) and displayed (user is
+probably reading the message).")
+(make-variable-buffer-local 'jabber-events-arrived)
+
+(defvar jabber-events-message ""
+ "Human-readable presentation of event information")
+(make-variable-buffer-local 'jabber-events-message)
+
+(defun jabber-events-update-message ()
+ (setq jabber-events-message
+ (concat (cdr (assq jabber-events-arrived
+ '((offline . "In offline storage")
+ (delivered . "Delivered")
+ (displayed . "Displayed"))))
+ (when jabber-events-composing-p
+ " (typing a message)"))))
+
+(add-hook 'jabber-chat-send-hooks 'jabber-events-when-sending)
+(defun jabber-events-when-sending (text id)
+ (setq jabber-events-arrived nil)
+ (jabber-events-update-message)
+ `((x ((xmlns . "jabber:x:event"))
+ ,@(mapcar #'list jabber-events-request-these))))
+
+;;; OUTGOING
+;;; Code for handling requests for event notifications and providing
+;;; them, modulo user preferences.
+
+(defcustom jabber-events-confirm-delivered t
+ "Send delivery confirmation if requested?"
+ :group 'jabber-events
+ :type 'boolean)
+
+(defcustom jabber-events-confirm-displayed t
+ "Send display confirmation if requested?"
+ :group 'jabber-events
+ :type 'boolean)
+
+(defcustom jabber-events-confirm-composing t
+ "Send notifications about typing a reply?"
+ :group 'jabber-events
+ :type 'boolean)
+
+(defvar jabber-events-requested ()
+ "List of events requested")
+(make-variable-buffer-local 'jabber-events-requested)
+
+(defvar jabber-events-last-id nil
+ "Id of last message received, or nil if none.")
+(make-variable-buffer-local 'jabber-events-last-id)
+
+(defvar jabber-events-delivery-confirmed nil
+ "Has delivery confirmation been sent?")
+(make-variable-buffer-local 'jabber-events-delivery-confirmed)
+
+(defvar jabber-events-display-confirmed nil
+ "Has display confirmation been sent?")
+(make-variable-buffer-local 'jabber-events-display-confirmed)
+
+(defvar jabber-events-composing-sent nil
+ "Has composing notification been sent?
+It can be sent and cancelled several times.")
+
+(add-hook 'window-configuration-change-hook
+ 'jabber-events-confirm-display)
+(defun jabber-events-confirm-display ()
+ "Send display confirmation if appropriate.
+That is, if user allows it, if the other user requested it,
+and it hasn't been sent before."
+ (walk-windows #'jabber-events-confirm-display-in-window))
+
+(defun jabber-events-confirm-display-in-window (window)
+ (with-current-buffer (window-buffer window)
+ (when (and jabber-events-confirm-displayed
+ (not jabber-events-display-confirmed)
+ (memq 'displayed jabber-events-requested)
+ ;; don't send to bare jids
+ (jabber-jid-resource jabber-chatting-with))
+ (jabber-send-sexp
+ `(message
+ ((to . ,jabber-chatting-with))
+ (x ((xmlns . "jabber:x:event"))
+ (displayed)
+ (id () ,jabber-events-last-id))))
+ (setq jabber-events-display-confirmed t))))
+
+(defun jabber-events-after-change ()
+ (let ((composing-now (not (eq (point-max) jabber-point-insert))))
+ (when (and jabber-events-confirm-composing
+ jabber-chatting-with
+ (not (eq composing-now jabber-events-composing-sent)))
+ (jabber-send-sexp
+ `(message
+ ((to . ,jabber-chatting-with))
+ (x ((xmlns . "jabber:x:event"))
+ ,@(if composing-now '((composing)) nil)
+ (id () ,jabber-events-last-id))))
+ (setq jabber-events-composing-sent composing-now))))
+
+;;; COMMON
+
+(add-to-list 'jabber-chat-printers 'jabber-handle-incoming-message-events)
+
+(defun jabber-handle-incoming-message-events (xml-data)
+ (let ((x (find "jabber:x:event"
+ (jabber-xml-get-children xml-data 'x)
+ :key #'(lambda (x) (jabber-xml-get-attribute x 'xmlns))
+ :test #'string=)))
+ ;; If there's a body, it's not an incoming message event.
+ (if (jabber-xml-get-children xml-data 'body)
+ ;; User is done composing, obviously.
+ (progn
+ (setq jabber-events-composing-p nil)
+ (jabber-events-update-message)
+
+ ;; Reset variables
+ (setq jabber-events-display-confirmed nil)
+ (setq jabber-events-delivery-confirmed nil)
+
+ ;; User requests message events
+ (setq jabber-events-requested
+ ;; There might be empty strings in the XML data,
+ ;; which car chokes on. Having nil values in
+ ;; the list won't hurt, therefore car-safe.
+ (mapcar #'car-safe
+ (jabber-xml-node-children x)))
+ (setq jabber-events-last-id (jabber-xml-get-attribute
+ xml-data 'id))
+
+ ;; Send notifications we already know about
+ (flet ((send-notification
+ (type)
+ (jabber-send-sexp
+ `(message
+ ((to . ,(jabber-xml-get-attribute xml-data 'from)))
+ (x ((xmlns . "jabber:x:event"))
+ (,type)
+ (id () ,jabber-events-last-id))))))
+ ;; Send delivery confirmation if appropriate
+ (when (and jabber-events-confirm-delivered
+ (memq 'delivered jabber-events-requested))
+ (send-notification 'delivered)
+ (setq jabber-events-delivery-confirmed t))
+
+ ;; Send display confirmation if appropriate
+ (when (and jabber-events-confirm-displayed
+ (get-buffer-window (current-buffer) 'visible)
+ (memq 'displayed jabber-events-requested))
+ (send-notification 'displayed)
+ (setq jabber-events-display-confirmed t))
+
+ ;; Set up hooks for composition notification
+ (when (and jabber-events-confirm-composing
+ (memq 'composing jabber-events-requested))
+ (add-hook 'post-command-hook 'jabber-events-after-change
+ nil t))))
+ ;; So it has no body. If it's a message event,
+ ;; the <x/> node should be the only child of the
+ ;; message, and it should contain an <id/> node.
+ ;; We check the latter.
+ (when (and x (jabber-xml-get-children x 'id))
+ ;; Currently we don't care about the <id/> node.
+
+ ;; There's only one node except for the id.
+ (unless
+ (dolist (possible-node '(offline delivered displayed))
+ (when (jabber-xml-get-children x possible-node)
+ (setq jabber-events-arrived possible-node)
+ (jabber-events-update-message)
+ (return t)))
+ ;; Or maybe even zero, which is a negative composing node.
+ (setq jabber-events-composing-p
+ (not (null (jabber-xml-get-children x 'composing))))
+ (jabber-events-update-message))))))
+
+(provide 'jabber-events)
+;; arch-tag: 7b6e61fe-a9b3-11d9-afca-000a95c2fcd0
diff --git a/jabber-export.el b/jabber-export.el
new file mode 100644
index 0000000..a71f729
--- /dev/null
+++ b/jabber-export.el
@@ -0,0 +1,234 @@
+;;; jabber-export.el --- export Jabber roster to file
+
+;; Copyright (C) 2005 Magnus Henoch
+
+;; Author: Magnus Henoch <mange@freemail.hu>
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+(require 'cl)
+
+(defvar jabber-export-roster-widget nil)
+
+(defvar jabber-import-subscription-p-widget nil)
+
+(defun jabber-export-roster (&optional roster)
+ "Create buffer from which roster can be exported to a file."
+ (interactive)
+ (with-current-buffer (get-buffer-create "Export roster")
+ (jabber-init-widget-buffer nil)
+
+ (widget-insert (jabber-propertize "Export roster\n"
+ 'face 'jabber-title-large))
+ (widget-insert "You are about to save your roster to a file. Here
+you can edit it before saving. Changes done here will
+not affect your actual roster.
+
+")
+
+ (widget-create 'push-button :notify #'jabber-export-save "Save to file")
+ (widget-insert " ")
+ (widget-create 'push-button :notify #'jabber-export-remove-regexp "Remove by regexp")
+ (widget-insert "\n\n")
+ (make-local-variable 'jabber-export-roster-widget)
+
+ (jabber-export-display (or roster (jabber-roster-to-sexp *jabber-roster*)))
+
+ (widget-setup)
+ (widget-minor-mode 1)
+ (goto-char (point-min))
+ (switch-to-buffer (current-buffer))))
+
+(defun jabber-import-roster (file)
+ "Create buffer for roster import from FILE."
+ (interactive "fImport roster from file: ")
+ (let ((roster
+ (with-temp-buffer
+ (let ((coding-system-for-read 'utf-8))
+ (jabber-roster-xml-to-sexp
+ (car (xml-parse-file file)))))))
+ (with-current-buffer (get-buffer-create "Import roster")
+ (jabber-init-widget-buffer nil)
+
+ (widget-insert (jabber-propertize "Import roster\n"
+ 'face 'jabber-title-large))
+ (widget-insert "You are about to import the contacts below to your roster.
+
+")
+
+ (make-local-variable 'jabber-import-subscription-p-widget)
+ (setq jabber-import-subscription-p-widget
+ (widget-create 'checkbox))
+ (widget-insert "Adjust subscriptions\n")
+
+ (widget-create 'push-button :notify #'jabber-import-doit "Import to roster")
+ (widget-insert " ")
+ (widget-create 'push-button :notify #'jabber-export-remove-regexp "Remove by regexp")
+ (widget-insert "\n\n")
+ (make-local-variable 'jabber-export-roster-widget)
+
+ (jabber-export-display roster)
+
+ (widget-setup)
+ (widget-minor-mode 1)
+ (goto-char (point-min))
+ (switch-to-buffer (current-buffer)))))
+
+(defun jabber-export-remove-regexp (&rest ignore)
+ (let* ((value (widget-value jabber-export-roster-widget))
+ (length-before (length value))
+ (regexp (read-string "Remove JIDs matching regexp: ")))
+ (setq value (delete-if
+ #'(lambda (a)
+ (string-match regexp (nth 0 a)))
+ value))
+ (widget-value-set jabber-export-roster-widget value)
+ (widget-setup)
+ (message "%d items removed" (- length-before (length value)))))
+
+(defun jabber-export-save (&rest ignore)
+ "Export roster to file."
+ (let ((items (mapcar #'jabber-roster-sexp-to-xml (widget-value jabber-export-roster-widget)))
+ (coding-system-for-write 'utf-8))
+ (with-temp-file (read-file-name "Export roster to file: ")
+ (insert "<iq xmlns='jabber:client'><query xmlns='jabber:iq:roster'>\n")
+ (dolist (item items)
+ (insert (jabber-sexp2xml item) "\n"))
+ (insert "</query></iq>\n"))
+ (message "Roster saved")))
+
+(defun jabber-import-doit (&rest ignore)
+ "Import roster being edited in widget."
+ (let (roster-delta)
+ (dolist (n (widget-value jabber-export-roster-widget))
+ (let* ((jid (nth 0 n))
+ (name (and (not (zerop (length (nth 1 n))))
+ (nth 1 n)))
+ (subscription (nth 2 n))
+ (groups (nth 3 n))
+ (jid-symbol (jabber-jid-symbol jid))
+ (in-roster-p (memq jid-symbol *jabber-roster*))
+ (jid-name (and in-roster-p (get jid-symbol 'name)))
+ (jid-subscription (and in-roster-p (get jid-symbol 'subscription)))
+ (jid-groups (and in-roster-p (get jid-symbol 'groups))))
+ ;; Do we need to change the roster?
+ (when (or
+ ;; If the contact is not in the roster already,
+ (not in-roster-p)
+ ;; or if the import introduces a name,
+ (and name (not jid-name))
+ ;; or changes a name,
+ (and name jid-name (not (string= name jid-name)))
+ ;; or introduces new groups.
+ (set-difference groups jid-groups :test #'string=))
+ (push (jabber-roster-sexp-to-xml
+ (list jid (or name jid-name) nil (union groups jid-groups :test #'string=))
+ t)
+ roster-delta))
+ ;; And adujst subscription.
+ (when (widget-value jabber-import-subscription-p-widget)
+ (let ((want-to (member subscription '("to" "both")))
+ (want-from (member subscription '("from" "both")))
+ (have-to (member jid-subscription '("to" "both")))
+ (have-from (member jid-subscription '("from" "both"))))
+ (flet ((request-subscription
+ (type)
+ (jabber-send-sexp `(presence ((to . ,jid)
+ (type . ,type))))))
+ (cond
+ ((and want-to (not have-to))
+ (request-subscription "subscribe"))
+ ((and have-to (not want-to))
+ (request-subscription "unsubscribe")))
+ (cond
+ ((and want-from (not have-from))
+ ;; not much to do here
+ )
+ ((and have-from (not want-from))
+ (request-subscription "unsubscribed"))))))))
+ (when roster-delta
+ (jabber-send-iq nil "set"
+ `(query ((xmlns . "jabber:iq:roster")) ,@roster-delta)
+ #'jabber-report-success "Roster import"
+ #'jabber-report-success "Roster import"))))
+
+(defun jabber-roster-to-sexp (roster)
+ "Convert ROSTER to simpler sexp format.
+Return a list, where each item is a vector:
+\[jid name subscription groups]
+where groups is a list of strings."
+ (mapcar
+ #'(lambda (n)
+ (list
+ (symbol-name n)
+ (or (get n 'name) "")
+ (get n 'subscription)
+ (get n 'groups)))
+ roster))
+
+(defun jabber-roster-sexp-to-xml (sexp &optional omit-subscription)
+ "Convert SEXP to XML format.
+Return an XML node."
+ `(item ((jid . ,(nth 0 sexp))
+ ,@(let ((name (nth 1 sexp)))
+ (unless (zerop (length name))
+ `((name . ,name))))
+ ,@(unless omit-subscription
+ `((subscription . ,(nth 2 sexp)))))
+ ,@(mapcar
+ #'(lambda (g)
+ (list 'group nil g))
+ (nth 3 sexp))))
+
+(defun jabber-roster-xml-to-sexp (xml-data)
+ "Convert XML-DATA to simpler sexp format.
+XML-DATA is an <iq> node with a <query xmlns='jabber:iq:roster'> child.
+See `jabber-roster-to-sexp' for description of output format."
+ (assert (eq (jabber-xml-node-name xml-data) 'iq))
+ (let ((query (car (jabber-xml-get-children xml-data 'query))))
+ (assert query)
+ (mapcar
+ #'(lambda (n)
+ (list
+ (jabber-xml-get-attribute n 'jid)
+ (or (jabber-xml-get-attribute n 'name) "")
+ (jabber-xml-get-attribute n 'subscription)
+ (mapcar
+ #'(lambda (g)
+ (car (jabber-xml-node-children g)))
+ (jabber-xml-get-children n 'group))))
+ (jabber-xml-get-children query 'item))))
+
+(defun jabber-export-display (roster)
+ (setq jabber-export-roster-widget
+ (widget-create
+ '(repeat
+ :tag "Roster"
+ (list :format "%v"
+ (string :tag "JID")
+ (string :tag "Name")
+ (choice :tag "Subscription"
+ (const "none")
+ (const "both")
+ (const "to")
+ (const "from"))
+ (repeat :tag "Groups"
+ (string :tag "Group"))))
+ :value roster)))
+
+(provide 'jabber-export)
+
+;;; arch-tag: 9c6b94a9-290a-4c0f-9286-72bd9c1fb8a3
diff --git a/jabber-feature-neg.el b/jabber-feature-neg.el
new file mode 100644
index 0000000..1fb8853
--- /dev/null
+++ b/jabber-feature-neg.el
@@ -0,0 +1,125 @@
+;; jabber-feature-neg.el - Feature Negotiation by JEP-0020
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-disco)
+(require 'cl)
+
+(add-to-list 'jabber-advertised-features "http://jabber.org/protocol/feature-neg")
+
+(defun jabber-fn-parse (xml-data type)
+ "Parse a Feature Negotiation request, return alist representation.
+XML-DATA should have one child element, <x/>, in the jabber:x:data
+namespace.
+
+TYPE is either 'request or 'response.
+
+Returned alist has field name as key, and value is a list of offered
+alternatives."
+ (let ((x (car (jabber-xml-get-children xml-data 'x))))
+ (unless (and x
+ (string= (jabber-xml-get-attribute x 'xmlns) "jabber:x:data"))
+ (jabber-signal-error "modify" 'bad-request "Malformed Feature Negotiation"))
+
+ (let (alist
+ (fields (jabber-xml-get-children x 'field)))
+ (dolist (field fields)
+ (let ((var (jabber-xml-get-attribute field 'var))
+ (value (car (jabber-xml-get-children field 'value)))
+ (options (jabber-xml-get-children field 'option)))
+ (setq alist (cons
+ (cons var
+ (cond
+ ((eq type 'request)
+ (mapcar #'(lambda (option)
+ (car (jabber-xml-node-children
+ (car (jabber-xml-get-children
+ option 'value)))))
+ options))
+ ((eq type 'response)
+ (jabber-xml-node-children value))
+ (t
+ (error "Incorrect Feature Negotiation type: %s" type))))
+ alist))))
+ ;; return alist
+ alist)))
+
+(defun jabber-fn-encode (alist type)
+ "Transform a feature alist into an <x/> node int the jabber:x:data namespace.
+Note that this is not the reverse of `jabber-fn-parse'.
+
+TYPE is either 'request or 'response."
+ (let ((requestp (eq type 'request)))
+ `(x ((xmlns . "jabber:x:data")
+ (type . ,(if requestp "form" "submit")))
+ ,@(mapcar #'(lambda (field)
+ `(field
+ ((type . "list-single")
+ (var . ,(car field)))
+ ,@(if requestp
+ (mapcar
+ #'(lambda (option)
+ `(option nil (value nil ,option)))
+ (cdr field))
+ (list `(value nil ,(cadr field))))))
+ alist))))
+
+(defun jabber-fn-intersection (mine theirs)
+ "Find values acceptable to both parties.
+
+MINE and THEIRS are alists, as returned by `jabber-fn-parse'.
+
+An alist is returned, where the keys are the negotiated variables,
+and the values are lists containing the preferred option. If
+negotiation is impossible, an error is signalled. The errors are as
+specified in JEP-0020, and not necessarily the ones of higher-level
+protocols."
+
+ (let ((vars (mapcar #'car mine))
+ (their-vars (mapcar #'car theirs)))
+
+ ;; are the same variables being negotiated?
+ (sort vars 'string-lessp)
+ (sort their-vars 'string-lessp)
+ (let ((mine-but-not-theirs (set-difference vars their-vars :test 'string=))
+ (theirs-but-not-mine (set-difference their-vars vars :test 'string=)))
+ (when mine-but-not-theirs
+ (jabber-signal-error "modify" 'not-acceptable (car mine-but-not-theirs)))
+ (when theirs-but-not-mine
+ (jabber-signal-error "cancel" 'feature-not-implemented (car theirs-but-not-mine))))
+
+ (let (alist)
+ (dolist (var vars)
+ (let ((my-options (cdr (assoc var mine)))
+ (their-options (cdr (assoc var theirs))))
+ (let ((common-options (intersection my-options their-options :test 'string=)))
+ (if common-options
+ ;; we have a match; but which one to use?
+ ;; the first one will probably work
+ (setq alist
+ (cons (list var (car common-options))
+ alist))
+ ;; no match
+ (jabber-signal-error "modify" 'not-acceptable var)))))
+ alist)))
+
+(provide 'jabber-feature-neg)
+
+;;; arch-tag: 65b2cdcc-7a5f-476b-a613-84ec8e590186
diff --git a/jabber-festival.el b/jabber-festival.el
new file mode 100644
index 0000000..23b0b96
--- /dev/null
+++ b/jabber-festival.el
@@ -0,0 +1,33 @@
+;;; jabber-festival.el --- Festival alert hooks
+
+;; Copyright (C) 2005 Magnus Henoch
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+(condition-case e
+ (progn
+ ;; Most people don't have Festival, so this will often fail
+ (require 'festival)
+ (define-jabber-alert festival "Voice messages through Festival"
+ 'festival-say-string))
+ (error nil))
+
+(provide 'jabber-festival)
+;; arch-tag: 8922D096-5D07-11D9-B4C2-000A95C2FCD0
+
+
diff --git a/jabber-ft-client.el b/jabber-ft-client.el
new file mode 100644
index 0000000..c3b9434
--- /dev/null
+++ b/jabber-ft-client.el
@@ -0,0 +1,54 @@
+;; jabber-ft-client.el - send file transfer requests, by JEP-0096
+
+;; Copyright (C) 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-si-client)
+(require 'jabber-util)
+
+(defun jabber-ft-send (jid filename desc)
+ "Attempt to send FILENAME to JID."
+ (interactive (list (jabber-read-jid-completing "Send file to: ")
+ (read-file-name "Send which file: " nil nil t)
+ (jabber-read-with-input-method "Description (optional): ")))
+ (if (zerop (length desc)) (setq desc nil))
+ (setq filename (expand-file-name filename))
+ (access-file filename "Couldn't open file")
+
+ (let* ((attributes (file-attributes filename))
+ (size (nth 7 attributes))
+ (date (nth 5 attributes)))
+ (jabber-si-initiate jid "http://jabber.org/protocol/si/profile/file-transfer"
+ `(file ((xmlns . "http://jabber.org/protocol/si/profile/file-transfer")
+ (name . ,(file-name-nondirectory filename))
+ (size . ,size)
+ (date . ,(jabber-encode-time date)))
+ (desc () ,desc))
+ `(lambda (jid sid send-data-function)
+ (jabber-ft-do-send jid sid send-data-function ,filename)))))
+
+(defun jabber-ft-do-send (jid sid send-data-function filename)
+ (with-temp-buffer
+ (insert-file-contents-literally filename)
+
+ ;; Ever heard of buffering?
+ (funcall send-data-function (buffer-string))
+ (message "File transfer completed")))
+
+(provide 'jabber-ft-client)
+;;; arch-tag: fba686d5-37b5-4165-86c5-49b76fa0ea6e
diff --git a/jabber-ft-server.el b/jabber-ft-server.el
new file mode 100644
index 0000000..09405f9
--- /dev/null
+++ b/jabber-ft-server.el
@@ -0,0 +1,104 @@
+;; jabber-ft-server.el - handle incoming file transfers, by JEP-0096
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-si-server)
+(require 'jabber-util)
+
+(defvar jabber-ft-sessions nil
+ "Alist, where keys are (sid jid), and values are buffers of the files.")
+
+(defvar jabber-ft-size nil
+ "Size of the file that is being downloaded")
+
+(add-to-list 'jabber-advertised-features "http://jabber.org/protocol/si/profile/file-transfer")
+
+(add-to-list 'jabber-si-profiles
+ (list "http://jabber.org/protocol/si/profile/file-transfer"
+ 'jabber-ft-accept
+ 'jabber-ft-data))
+
+(defun jabber-ft-accept (xml-data)
+ "Receive IQ stanza containing file transfer request, ask user"
+ (let* ((from (jabber-xml-get-attribute xml-data 'from))
+ (query (jabber-iq-query xml-data))
+ (si-id (jabber-xml-get-attribute query 'id))
+ ;; TODO: check namespace
+ (file (car (jabber-xml-get-children query 'file)))
+ (name (jabber-xml-get-attribute file 'name))
+ (size (jabber-xml-get-attribute file 'size))
+ (date (jabber-xml-get-attribute file 'date))
+ (md5-hash (jabber-xml-get-attribute file 'hash))
+ (desc (car (jabber-xml-node-children
+ (car (jabber-xml-get-children file 'desc)))))
+ (range (car (jabber-xml-get-children file 'range))))
+ (unless (and name size)
+ ;; both name and size must be present
+ (jabber-signal-error "modify" 'bad-request))
+
+ (let ((question (format
+ "%s is sending you the file %s (%s bytes).%s Accept? "
+ (jabber-jid-displayname from)
+ name
+ size
+ (if (not (zerop (length desc)))
+ (concat " Description: '" desc "'")
+ ""))))
+ (unless (yes-or-no-p question)
+ (jabber-signal-error "cancel" 'forbidden)))
+
+ ;; default is to save with given name, in current directory.
+ ;; maybe that's bad; maybe should be customizable.
+ (let* ((file-name (read-file-name "Download to: " nil nil nil name))
+ (buffer (create-file-buffer file-name)))
+ (message "Starting download of %s..." (file-name-nondirectory file-name))
+ (with-current-buffer buffer
+ (setq buffer-file-coding-system 'binary)
+ ;; For Emacs, switch buffer to unibyte _before_ anything goes into it,
+ ;; otherwise binary files are corrupted. For XEmacs, it isn't needed,
+ ;; and it also doesn't have set-buffer-multibyte.
+ (if (fboundp 'set-buffer-multibyte)
+ (set-buffer-multibyte nil))
+ (set-visited-file-name file-name t)
+ (make-local-variable 'jabber-ft-size)
+ (setq jabber-ft-size (string-to-number size)))
+ (add-to-list 'jabber-ft-sessions
+ (cons (list si-id from) buffer)))
+
+ ;; to support range, return something sensible here
+ nil))
+
+(defun jabber-ft-data (jid sid data)
+ "Receive chunk of transferred file."
+ (let ((buffer (cdr (assoc (list sid jid) jabber-ft-sessions))))
+ (with-current-buffer buffer
+ ;; If data is nil, there is no more data.
+ ;; But maybe the remote entity doesn't close the stream -
+ ;; then we have to keep track of file size to know when to stop.
+ (when data
+ (insert data))
+ (if (and data (< (buffer-size) jabber-ft-size))
+ t
+ (basic-save-buffer)
+ (message "%s downloaded" (file-name-nondirectory buffer-file-name))))))
+
+(provide 'jabber-ft-server)
+
+;;; arch-tag: 334adcff-6210-496e-8382-8f49ae0248a1
diff --git a/jabber-history.el b/jabber-history.el
new file mode 100644
index 0000000..669fe2a
--- /dev/null
+++ b/jabber-history.el
@@ -0,0 +1,244 @@
+;; jabber-history.el - recording message history
+
+;; Copyright (C) 2004 - Mathias Dahl
+;; Copyright (C) 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+;;; Log format:
+;; Each message is on one separate line, represented as a vector with
+;; five elements. The first element is time encoded according to
+;; JEP-0082. The second element is direction, "in" or "out".
+;; The third element is the sender, "me" or a JID. The fourth
+;; element is the recipient. The fifth element is the text
+;; of the message.
+
+;; FIXME: when rotation is enabled, jabber-history-query won't look
+;; for older history files if the current history file doesn't contain
+;; enough backlog entries.
+
+(require 'jabber-core)
+
+(defgroup jabber-history nil "Customization options for Emacs
+Jabber history files."
+ :group 'jabber)
+
+(defcustom jabber-history-enabled nil
+ "Non-nil means message logging is enabled."
+ :type 'boolean
+ :group 'jabber-history)
+
+(defcustom jabber-use-global-history t
+ "Indicate whether Emacs Jabber should use a global file for
+ store messages. If non-nil, jabber-global-history-filename is
+ used, otherwise, messages are stored in per-user files under
+ the jabber-history-dir directory."
+ :type 'boolean
+ :group 'jabber-history)
+
+(defcustom jabber-history-dir "~/.emacs-jabber"
+ "Base directory where per-contact history files are stored.
+ Used only when jabber-use-global-history is not true."
+ :type 'directory
+ :group 'jabber-history)
+
+(defcustom jabber-global-history-filename "~/.jabber_global_message_log"
+ "Global file where all messages are logged. Used when
+ jabber-use-global-history is non-nil."
+ :type 'file
+ :group 'jabber-history)
+
+(defcustom jabber-history-enable-rotation nil
+ "Whether history files should be renamed when reach
+ jabber-history-size-limit kilobytes. If nil, history files
+ will grow indefinitely, otherwise they'll be renamed to
+ <history-file>-<number>, where <number> is 1 or the smallest
+ number after the last rotation."
+ :type 'boolean
+ :group 'jabber-history)
+
+(defcustom jabber-history-size-limit 1024
+ "Maximum history file size in kilobytes. When history file
+ reaches this limit, it is renamed to <history-file>-<number>,
+ where <number> is 1 or the smallest number after the last
+ rotation."
+ :type 'integer
+ :group 'jabber-history)
+
+
+(defun jabber-rotate-history-p (history-file)
+ "Return true if HISTORY-FILE should be rotated."
+ (when (and jabber-history-enable-rotation
+ (file-exists-p history-file))
+ (> (/ (nth 7 (file-attributes history-file)) 1024)
+ jabber-history-size-limit)))
+
+(defun jabber-history-rotate (history-file &optional try)
+ "Rename HISTORY-FILE to HISTORY-FILE-TRY."
+ (let ((suffix (number-to-string (or try 1))))
+ (if (file-exists-p (concat history-file "-" suffix))
+ (jabber-history-rotate history-file (if try (1+ try) 1))
+ (rename-file history-file (concat history-file "-" suffix)))))
+
+(add-to-list 'jabber-message-chain 'jabber-message-history)
+(defun jabber-message-history (xml-data)
+ "Log message to log file."
+ (when (and (not jabber-use-global-history)
+ (not (file-directory-p jabber-history-dir)))
+ (make-directory jabber-history-dir))
+ (if (and jabber-history-enabled (not (jabber-muc-message-p xml-data)))
+ (let ((from (jabber-xml-get-attribute xml-data 'from))
+ (text (car (jabber-xml-node-children
+ (car (jabber-xml-get-children xml-data 'body)))))
+ (timestamp (car (delq nil (mapcar 'jabber-x-delay (jabber-xml-get-children xml-data 'x))))))
+ (when (and from text)
+ (jabber-history-log-message "in" from nil text timestamp)))))
+
+(add-hook 'jabber-chat-send-hooks 'jabber-history-send-hook)
+
+(defun jabber-history-send-hook (body id)
+ "Log outgoing message to log file."
+ (when (and (not jabber-use-global-history)
+ (not (file-directory-p jabber-history-dir)))
+ (make-directory jabber-history-dir))
+ ;; This function is called from a chat buffer, so jabber-chatting-with
+ ;; contains the desired value.
+ (if jabber-history-enabled
+ (jabber-history-log-message "out" nil jabber-chatting-with body (current-time))))
+
+(defun jabber-history-filename (contact)
+ "Return a history filename for CONTACT if the per-user file
+ loggin strategy is used or the global history filename."
+ (if jabber-use-global-history
+ jabber-global-history-filename
+ (concat jabber-history-dir "/" (jabber-jid-user contact))))
+
+(defun jabber-history-log-message (direction from to body timestamp)
+ "Log a message"
+ (with-temp-buffer
+ ;; Remove properties
+ (set-text-properties 0 (length body) nil body)
+ ;; Encode text as Lisp string - get decoding for free
+ (setq body (prin1-to-string body))
+ ;; Encode LF and CR
+ (while (string-match "\n" body)
+ (setq body (replace-match "\\n" nil t body nil)))
+ (while (string-match "\r" body)
+ (setq body (replace-match "\\r" nil t body nil)))
+ (insert (format "[\"%s\" \"%s\" \"%s\" \"%s\" %s]\n"
+ (jabber-encode-time (or timestamp (current-time)))
+ (or direction
+ "in")
+ (or from
+ "me")
+ (or to
+ "me")
+ body))
+ (let ((coding-system-for-write 'utf-8)
+ (history-file (jabber-history-filename (or from to))))
+ (when (and (not jabber-use-global-history)
+ (not (file-directory-p jabber-history-dir)))
+ (make-directory jabber-history-dir))
+ (when (jabber-rotate-history-p history-file)
+ (jabber-history-rotate history-file))
+ (write-region (point-min) (point-max) history-file t 'quiet))))
+
+(defun jabber-history-query (start-time
+ end-time
+ number
+ direction
+ jid-regexp
+ history-file)
+ "Return a list of vectors, one for each message matching the criteria.
+START-TIME and END-TIME are floats as obtained from `float-time'.
+Either or both may be nil, meaning no restriction.
+NUMBER is the maximum number of messages to return, or t for
+unlimited.
+DIRECTION is either \"in\" or \"out\", or t for no limit on direction.
+JID-REGEXP is a regexp which must match the JID.
+HISTORY-FILE is the file in which to search.
+
+Currently jabber-history-query performs a linear search from the end
+of the log file."
+ (when (file-readable-p history-file)
+ (with-temp-buffer
+ (let ((coding-system-for-read 'utf-8))
+ (insert-file-contents history-file))
+ (let (collected current-line)
+ (goto-char (point-max))
+ (catch 'beginning-of-file
+ (while (progn
+ (backward-sexp)
+ (setq current-line (car (read-from-string
+ (buffer-substring
+ (point)
+ (save-excursion
+ (forward-sexp)
+ (point))))))
+ (and (or (null start-time)
+ (> (jabber-float-time (jabber-parse-time
+ (aref current-line 0)))
+ start-time))
+ (or (eq number t)
+ (< (length collected) number))))
+ (if (and (or (eq direction t)
+ (string= direction (aref current-line 1)))
+ (or (null end-time)
+ (> end-time (jabber-float-time (jabber-parse-time
+ (aref current-line 0)))))
+ (string-match
+ jid-regexp
+ (car
+ (remove "me"
+ (list (aref current-line 2)
+ (aref current-line 3))))))
+ (push current-line collected))
+ (when (bobp)
+ (throw 'beginning-of-file nil))))
+ collected))))
+
+(defcustom jabber-backlog-days 3.0
+ "Age limit on messages in chat buffer backlog, in days"
+ :group 'jabber
+ :type '(choice (number :tag "Number of days")
+ (const :tag "No limit" nil)))
+
+(defcustom jabber-backlog-number 10
+ "Maximum number of messages in chat buffer backlog"
+ :group 'jabber
+ :type 'integer)
+
+(defun jabber-history-backlog (jid &optional before)
+ "Fetch context from previous chats with JID.
+Return a list of history entries (vectors), limited by
+`jabber-backlog-days' and `jabber-backlog-number'.
+If BEFORE is non-nil, it should be a float-time after which
+no entries will be fetched. `jabber-backlog-days' still
+applies, though."
+ (interactive)
+ (jabber-history-query
+ (and jabber-backlog-days
+ (- (jabber-float-time) (* jabber-backlog-days 86400.0)))
+ before
+ jabber-backlog-number
+ t ; both incoming and outgoing
+ (concat "^" (regexp-quote (jabber-jid-user jid)) "\\(/.*\\)?$")
+ (jabber-history-filename jid)))
+
+(provide 'jabber-history)
+
+;; arch-tag: 0AA0C235-3FC0-11D9-9FE7-000A95C2FCD0
diff --git a/jabber-iq.el b/jabber-iq.el
new file mode 100644
index 0000000..a165517
--- /dev/null
+++ b/jabber-iq.el
@@ -0,0 +1,177 @@
+;; jabber-iq.el - infoquery functions
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-core)
+(require 'jabber-util)
+(require 'jabber-keymap)
+
+(defvar *jabber-open-info-queries* nil
+ "an alist of open query id and their callback functions")
+
+(defvar jabber-iq-get-xmlns-alist nil
+ "Mapping from XML namespace to handler for IQ GET requests.")
+
+(defvar jabber-iq-set-xmlns-alist nil
+ "Mapping from XML namespace to handler for IQ SET requests.")
+
+(defvar jabber-browse-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map jabber-common-keymap)
+ (define-key map [mouse-2] 'jabber-popup-combined-menu)
+ map))
+
+(defcustom jabber-browse-mode-hook nil
+ "Hook run when entering Browse mode."
+ :group 'jabber
+ :type 'hook)
+
+(defun jabber-browse-mode ()
+"\\{jabber-browse-mode-map}"
+ (kill-all-local-variables)
+ (setq major-mode 'jabber-browse-mode
+ mode-name "jabber-browse")
+ (use-local-map jabber-browse-mode-map)
+ (setq buffer-read-only t)
+ (if (fboundp 'run-mode-hooks)
+ (run-mode-hooks 'jabber-browse-mode-hook)
+ (run-hooks 'jabber-browse-mode-hook)))
+
+(put 'jabber-browse-mode 'mode-class 'special)
+
+(add-to-list 'jabber-iq-chain 'jabber-process-iq)
+(defun jabber-process-iq (xml-data)
+ "process an incoming iq stanza"
+ (let* ((id (jabber-xml-get-attribute xml-data 'id))
+ (type (jabber-xml-get-attribute xml-data 'type))
+ (from (jabber-xml-get-attribute xml-data 'from))
+ (query (jabber-iq-query xml-data))
+ (callback (assoc id *jabber-open-info-queries*)))
+ (cond
+ ;; if type is "result" or "error", this is a response to a query we sent.
+ ((or (string= type "result")
+ (string= type "error"))
+ (let ((callback-cons (nth (cdr (assoc type '(("result" . 0)
+ ("error" . 1)))) (cdr callback))))
+ (if (consp callback-cons)
+ (funcall (car callback-cons) xml-data (cdr callback-cons))))
+ (setq *jabber-open-info-queries* (delq callback *jabber-open-info-queries*)))
+
+ ;; if type is "get" or "set", correct action depends on namespace of request.
+ ((and (listp query)
+ (or (string= type "get")
+ (string= type "set")))
+ (let* ((which-alist (eval (cdr (assoc type
+ (list
+ (cons "get" 'jabber-iq-get-xmlns-alist)
+ (cons "set" 'jabber-iq-set-xmlns-alist))))))
+ (handler (cdr (assoc (jabber-xml-get-attribute query 'xmlns) which-alist))))
+ (if handler
+ (condition-case error-var
+ (funcall handler xml-data)
+ (jabber-error
+ (apply 'jabber-send-iq-error from id query (cdr error-var)))
+ (error (jabber-send-iq-error from id query "wait" 'internal-server-error (error-message-string error-var))))
+ (jabber-send-iq-error from id query "cancel" 'feature-not-implemented)))))))
+
+(defun jabber-send-iq (to type query success-callback success-closure-data
+ error-callback error-closure-data &optional result-id)
+ "Send an iq stanza to the specified entity, and optionally set up a callback.
+TO is the addressee.
+TYPE is one of \"get\", \"set\", \"result\" or \"error\".
+QUERY is a list containing the child of the iq node in the format `jabber-sexp2xml'
+accepts.
+SUCCESS-CALLBACK is the function to be called when a successful result arrives.
+SUCCESS-CLOSURE-DATA is the second argument to SUCCESS-CALLBACK.
+ERROR-CALLBACK is the function to be called when an error arrives.
+ERROR-CLOSURE-DATA is the second argument to ERROR-CALLBACK.
+RESULT-ID is the id to be used for a response to a received iq message.
+`jabber-report-success' and `jabber-process-data' are common callbacks."
+ (let ((id (or result-id (apply 'format "emacs-iq-%d.%d.%d" (current-time)))))
+ (if (or success-callback error-callback)
+ (setq *jabber-open-info-queries* (cons (list id
+ (cons success-callback success-closure-data)
+ (cons error-callback error-closure-data))
+
+ *jabber-open-info-queries*)))
+ (jabber-send-sexp (list 'iq (append
+ (if to (list (cons 'to to)))
+ (list (cons 'type type))
+ (list (cons 'id id)))
+ query))))
+
+(defun jabber-send-iq-error (to id original-query error-type condition
+ &optional text app-specific)
+ "Send an error iq stanza to the specified entity in response to a
+previously sent iq stanza.
+TO is the addressee.
+ID is the id of the iq stanza that caused the error.
+ORIGINAL-QUERY is the original query, which should be included in the
+error, or nil.
+ERROR-TYPE is one of \"cancel\", \"continue\", \"modify\", \"auth\"
+and \"wait\".
+CONDITION is a symbol denoting a defined XMPP condition.
+TEXT is a string to be sent in the error message, or nil for no text.
+APP-SPECIFIC is a list of extra XML tags.
+
+See section 9.3 of XMPP Core."
+ (jabber-send-sexp `(iq ((to . ,to)
+ (type . "error")
+ (id . ,id))
+ ,original-query
+ (error ((type . ,error-type))
+ (,condition ((xmlns . "urn:ietf:params:xml:ns:xmpp-stanzas")))
+ ,(if text
+ `(text ((xmlns . "urn:ietf:params:xml:ns:xmpp-stanzas"))
+ ,text))
+ ,@app-specific))))
+
+(defun jabber-process-data (xml-data closure-data)
+ "Process random results from various requests."
+ (let ((from (or (jabber-xml-get-attribute xml-data 'from) jabber-server))
+ (xmlns (jabber-iq-xmlns xml-data))
+ (type (jabber-xml-get-attribute xml-data 'type)))
+ (with-current-buffer (get-buffer-create (concat "*-jabber-browse-:-" from "-*"))
+ (if (not (eq major-mode 'jabber-browse-mode))
+ (jabber-browse-mode))
+
+ (setq buffer-read-only nil)
+ (goto-char (point-max))
+
+ (insert (jabber-propertize from
+ 'face 'jabber-title-large) "\n\n")
+
+ ;; If closure-data is a function, call it. If it is a string,
+ ;; output it along with a description of the error. For other
+ ;; values (e.g. nil), just dump the XML.
+ (cond
+ ((functionp closure-data)
+ (funcall closure-data xml-data))
+ ((stringp closure-data)
+ (insert closure-data ": " (jabber-parse-error (jabber-iq-error xml-data)) "\n\n"))
+ (t
+ (insert (format "%S\n\n" xml-data))))
+
+ (dolist (hook '(jabber-info-message-hooks jabber-alert-info-message-hooks))
+ (run-hook-with-args hook 'browse (current-buffer) (funcall jabber-alert-info-message-function 'browse (current-buffer)))))))
+
+(provide 'jabber-iq)
+
+;;; arch-tag: 5585dfa3-b59a-42ee-9292-803652c85e26
diff --git a/jabber-keepalive.el b/jabber-keepalive.el
new file mode 100644
index 0000000..7c43ecb
--- /dev/null
+++ b/jabber-keepalive.el
@@ -0,0 +1,95 @@
+;; jabber-keepalive.el - try to detect lost connection
+
+;; Copyright (C) 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+;;; These keepalive functions send a jabber:iq:time request to the
+;;; server every X minutes, and considers the connection broken if
+;;; they get no answer within Y seconds.
+
+(defgroup jabber-keepalive nil
+ "Keepalive functions try to detect lost connection"
+ :group 'jabber)
+
+(defcustom jabber-keepalive-interval 600
+ "Interval in seconds between connection checks."
+ :type 'integer
+ :group 'jabber-keepalive)
+
+(defcustom jabber-keepalive-timeout 20
+ "Seconds to wait for response from server."
+ :type 'integer
+ :group 'jabber-keepalive)
+
+(defvar jabber-keepalive-timer nil
+ "Timer object for keepalive function")
+
+(defvar jabber-keepalive-timeout-timer nil
+ "Timer object for keepalive timeout function")
+
+(defun jabber-keepalive-start ()
+ "Activate keepalive"
+ (interactive)
+
+ (when jabber-keepalive-timer
+ (jabber-keepalive-stop))
+
+ (setq jabber-keepalive-timer
+ (run-with-timer 5
+ jabber-keepalive-interval
+ 'jabber-keepalive-do))
+ (add-hook 'jabber-post-disconnect-hook 'jabber-keepalive-stop))
+
+(defun jabber-keepalive-stop()
+ "Deactivate keepalive"
+ (interactive)
+
+ (when jabber-keepalive-timer
+ (jabber-cancel-timer jabber-keepalive-timer)
+ (setq jabber-keepalive-timer nil)))
+
+(defun jabber-keepalive-do ()
+ (message "%s: sending keepalive packet" (current-time-string))
+ (setq jabber-keepalive-timeout-timer
+ (run-with-timer jabber-keepalive-timeout
+ nil
+ 'jabber-keepalive-timeout))
+
+ ;; Whether we get an error or not is not interesting.
+ ;; Getting a response at all is.
+ (jabber-send-iq jabber-server "get"
+ '(query ((xmlns . "jabber:iq:time")))
+ 'jabber-keepalive-got-response nil
+ 'jabber-keepalive-got-response nil))
+
+(defun jabber-keepalive-got-response (&rest args)
+ (message "%s: got keepalive response" (current-time-string))
+ (jabber-cancel-timer jabber-keepalive-timeout-timer)
+ (setq jabber-keepalive-timeout-timer nil))
+
+(defun jabber-keepalive-timeout ()
+ (message "%s: keepalive timeout, connection considered lost" (current-time-string))
+ (jabber-cancel-timer jabber-keepalive-timer)
+ (setq jabber-keepalive-timer nil)
+
+ (run-hooks jabber-lost-connection-hook)
+ (jabber-disconnect))
+
+(provide 'jabber-keepalive)
+
+;;; arch-tag: d19ca743-75a1-475f-9217-83bd18012146
diff --git a/jabber-keymap.el b/jabber-keymap.el
new file mode 100644
index 0000000..f8b50f4
--- /dev/null
+++ b/jabber-keymap.el
@@ -0,0 +1,58 @@
+;; jabber-keymap.el - common keymap for many modes
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+;; button.el was introduced in Emacs 22
+(condition-case e
+ (require 'button)
+ (error nil))
+
+(defvar jabber-common-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-c\C-c" 'jabber-popup-chat-menu)
+ (define-key map "\C-c\C-r" 'jabber-popup-roster-menu)
+ (define-key map "\C-c\C-i" 'jabber-popup-info-menu)
+ (define-key map "\C-c\C-m" 'jabber-popup-muc-menu)
+ (define-key map "\C-c\C-s" 'jabber-popup-service-menu)
+ ;; note that {forward,backward}-button are not autoloaded.
+ ;; thus the `require' above.
+ (when (fboundp 'forward-button)
+ (define-key map [?\t] 'forward-button)
+ (define-key map [backtab] 'backward-button))
+ map))
+
+(defvar jabber-global-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-c" 'jabber-connect)
+ (define-key map "\C-d" 'jabber-disconnect)
+ (define-key map "\C-r" 'jabber-switch-to-roster-buffer)
+ (define-key map "\C-j" 'jabber-chat-with)
+ (define-key map "\C-a" 'jabber-send-away-presence)
+ (define-key map "\C-o" 'jabber-send-default-presence)
+ (define-key map "\C-x" 'jabber-send-xa-presence)
+ map)
+ "Global Jabber keymap (usually under C-x C-j)")
+
+(define-key ctl-x-map "\C-j" jabber-global-keymap)
+
+(provide 'jabber-keymap)
+
+;;; arch-tag: 22a9993d-a4a7-40ef-a025-7cff6c3f5587
diff --git a/jabber-logon.el b/jabber-logon.el
new file mode 100644
index 0000000..f87aceb
--- /dev/null
+++ b/jabber-logon.el
@@ -0,0 +1,88 @@
+;; jabber-logon.el - logon functions
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-xml)
+(require 'jabber-util)
+;; sha1-el is known under two names
+(condition-case e
+ (require 'sha1)
+ (error (require 'sha1-el)))
+
+(defun jabber-get-auth (to)
+ "Send IQ get request in namespace \"jabber:iq:auth\"."
+ (jabber-send-iq to
+ "get"
+ `(query ((xmlns . "jabber:iq:auth"))
+ (username () ,jabber-username))
+ #'jabber-do-logon nil
+ #'jabber-report-success "Impossible error - auth field request"))
+
+(defun jabber-do-logon (xml-data closure-data)
+ "send username and password in logon attempt"
+ (cond
+ ((string= (jabber-xml-get-attribute xml-data 'type) "result")
+ (let (auth)
+ (if (jabber-xml-get-children (jabber-iq-query xml-data) 'digest)
+ ;; SHA1 digest passwords allowed
+ (let ((passwd (jabber-read-passwd)))
+ (if passwd
+ (setq auth `(digest () ,(sha1 (concat jabber-session-id passwd))))))
+ (if (yes-or-no-p "Jabber server only allows cleartext password transmission! Continue? ")
+ (let ((passwd (jabber-read-passwd)))
+ (if passwd
+ (setq auth `(password () ,passwd))))))
+
+ ;; If auth is still nil, user cancelled process somewhere
+ (if auth
+ (jabber-send-iq jabber-server
+ "set"
+ `(query ((xmlns . "jabber:iq:auth"))
+ (username () ,jabber-username)
+ ,auth
+ (resource () ,jabber-resource))
+ #'jabber-process-logon t
+ #'jabber-process-logon nil)
+ (jabber-disconnect))))
+ (t
+ (error "Logon error ended up in the wrong place"))))
+
+(defun jabber-process-logon (xml-data closure-data)
+ "receive login success or failure, and request roster.
+CLOSURE-DATA should be t on success and nil on failure."
+ (if closure-data
+ ;; Logon success
+ (progn
+ (setq *jabber-authenticated* t)
+ (jabber-send-iq nil
+ "get"
+ '(query ((xmlns . "jabber:iq:roster")))
+ #'jabber-process-roster 'initial
+ #'jabber-report-success "Roster retrieval")
+
+ (run-hooks 'jabber-post-connect-hook))
+
+ ;; Logon failure
+ (jabber-report-success xml-data "Logon")
+ (jabber-disconnect)))
+
+(provide 'jabber-logon)
+
+;;; arch-tag: f24ebe5e-3420-44bb-af81-d4de21f378b0
diff --git a/jabber-menu.el b/jabber-menu.el
new file mode 100644
index 0000000..4387c76
--- /dev/null
+++ b/jabber-menu.el
@@ -0,0 +1,141 @@
+;; jabber-menu.el - menu definitions
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(defvar jabber-menu (make-sparse-keymap "jabber-menu"))
+
+(defun jabber-menu (&optional remove)
+ "Put \"Jabber\" menu on menubar.
+With prefix argument, remove it."
+ (interactive "P")
+ (define-key global-map
+ [menu-bar jabber-menu]
+ (and (not remove) (cons "Jabber" jabber-menu))))
+
+(define-key jabber-menu
+ [jabber-menu-connect]
+ '("Connect" . jabber-connect))
+
+(define-key jabber-menu
+ [jabber-menu-disconnect]
+ '("Disconnect" . jabber-disconnect))
+
+(define-key jabber-menu
+ [jabber-menu-roster]
+ '("Switch to roster" . jabber-switch-to-roster-buffer))
+
+(define-key jabber-menu
+ [jabber-menu-customize]
+ '("Customize" . jabber-customize))
+
+(define-key jabber-menu
+ [jabber-menu-info]
+ '("Help" . jabber-info))
+
+(define-key jabber-menu
+ [jabber-menu-status]
+ (cons "Set Status" (make-sparse-keymap "set-status")))
+
+(defmacro jabber-define-status-key (title show)
+ (list 'let (list ( list 'func (list 'make-symbol (list 'concat "jabber-send-presence-" show)))
+ (list 'menu-item (list 'make-symbol (list 'concat "jabber-menu-status-" show))))
+ (list 'fset 'func `(lambda () (interactive)
+ (jabber-send-presence ,show
+ (jabber-read-with-input-method "status message: " *jabber-current-status* '*jabber-status-history*)
+ (format "%d" *jabber-current-priority*))))
+ (list 'define-key 'jabber-menu
+ (list 'vector ''jabber-menu-status 'menu-item)
+ (list 'cons title 'func))))
+
+;;;(dolist (presence jabber-presence-strings)
+;;; (jabber-define-status-key (cdr presence) (car presence)))
+;;(jabber-define-status-key "Online" "")
+
+(jabber-define-status-key "Chatty" "chat")
+;;(jabber-define-status-key "Away" "away")
+;;(jabber-define-status-key "Extended Away" "xa")
+(jabber-define-status-key "Do not Disturb" "dnd")
+(define-key jabber-menu
+ [jabber-menu-status jabber-menu-status-xa]
+ '("Extended Away" . jabber-send-xa-presence))
+(define-key jabber-menu
+ [jabber-menu-status jabber-menu-status-away]
+ '("Away" . jabber-send-away-presence))
+(define-key jabber-menu
+ [jabber-menu-status jabber-menu-status-online]
+ '("Online" . jabber-send-default-presence))
+
+(defvar jabber-jid-chat-menu nil
+ "Menu items for chat menu")
+
+(defvar jabber-jid-info-menu nil
+ "Menu item for info menu")
+
+(defvar jabber-jid-roster-menu nil
+ "Menu items for roster menu")
+
+(defvar jabber-jid-muc-menu nil
+ "Menu items for MUC menu")
+
+(defvar jabber-jid-service-menu nil
+ "Menu items for service menu")
+
+(defun jabber-popup-menu (which-menu)
+ "Popup specified menu"
+ (let* ((mouse-event (and (listp last-input-event) last-input-event))
+ (choice (widget-choose "Actions" which-menu mouse-event)))
+ (if mouse-event
+ (mouse-set-point mouse-event))
+ (if choice
+ (call-interactively choice))))
+
+(defun jabber-popup-chat-menu ()
+ "Popup chat menu"
+ (interactive)
+ (jabber-popup-menu jabber-jid-chat-menu))
+
+(defun jabber-popup-info-menu ()
+ "Popup info menu"
+ (interactive)
+ (jabber-popup-menu jabber-jid-info-menu))
+
+(defun jabber-popup-roster-menu ()
+ "Popup roster menu"
+ (interactive)
+ (jabber-popup-menu jabber-jid-roster-menu))
+
+(defun jabber-popup-muc-menu ()
+ "Popup MUC menu"
+ (interactive)
+ (jabber-popup-menu jabber-jid-muc-menu))
+
+(defun jabber-popup-service-menu ()
+ "Popup service menu"
+ (interactive)
+ (jabber-popup-menu jabber-jid-service-menu))
+
+(defun jabber-popup-combined-menu ()
+ "Popup combined menu"
+ (interactive)
+ (jabber-popup-menu (append jabber-jid-chat-menu jabber-jid-info-menu jabber-jid-roster-menu jabber-jid-muc-menu)))
+
+(provide 'jabber-menu)
+
+;;; arch-tag: 5147f52f-de47-4348-86ff-b799d7a75e3f
diff --git a/jabber-modeline.el b/jabber-modeline.el
new file mode 100644
index 0000000..6d41093
--- /dev/null
+++ b/jabber-modeline.el
@@ -0,0 +1,95 @@
+;; jabber-modeline.el - display jabber status in modeline
+
+;; Copyright (C) 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-presence)
+(require 'jabber-alert)
+(eval-when-compile (require 'cl))
+
+(defgroup jabber-mode-line nil
+ "Display Jabber status in mode line"
+ :group 'jabber)
+
+(defcustom jabber-mode-line-compact t
+ "Count contacts in fewer categories for compact view"
+ :group 'jabber-mode-line
+ :type 'boolean)
+
+(defvar jabber-mode-line-string nil)
+(defvar jabber-mode-line-presence nil)
+(defvar jabber-mode-line-contacts nil)
+
+(defadvice jabber-send-presence (after jsp-update-mode-line
+ (show status priority))
+ (jabber-mode-line-presence-update))
+
+(defun jabber-mode-line-presence-update ()
+ (setq jabber-mode-line-presence (if (and *jabber-connected* (not *jabber-disconnecting*))
+ (cdr (assoc *jabber-current-show* jabber-presence-strings))
+ "Offline")))
+
+(defun jabber-mode-line-count-contacts (&rest ignore)
+ (let ((count (list (cons "chat" 0)
+ (cons "" 0)
+ (cons "away" 0)
+ (cons "xa" 0)
+ (cons "dnd" 0)
+ (cons nil 0))))
+ (dolist (buddy *jabber-roster*)
+ (when (assoc (get buddy 'show) count)
+ (incf (cdr (assoc (get buddy 'show) count)))))
+ (setq jabber-mode-line-contacts
+ (if jabber-mode-line-compact
+ (format "(%d/%d/%d)"
+ (+ (cdr (assoc "chat" count))
+ (cdr (assoc "" count)))
+ (+ (cdr (assoc "away" count))
+ (cdr (assoc "xa" count))
+ (cdr (assoc "dnd" count)))
+ (cdr (assoc nil count)))
+ (apply 'format "(%d/%d/%d/%d/%d/%d)"
+ (mapcar 'cdr count))))))
+
+(define-minor-mode jabber-mode-line-mode
+ "Toggle display of Jabber status in mode lines.
+Display consists of your own status, and six numbers
+meaning the number of chatty, online, away, xa, dnd
+and offline contacts, respectively."
+ :global t :group 'jabber-mode-line
+ (setq jabber-mode-line-string "")
+ (or global-mode-string (setq global-mode-string '("")))
+ (if jabber-mode-line-mode
+ (progn
+ (add-to-list 'global-mode-string 'jabber-mode-line-string t)
+
+ (setq jabber-mode-line-string (list " "
+ 'jabber-mode-line-presence
+ " "
+ 'jabber-mode-line-contacts))
+ (jabber-mode-line-presence-update)
+ (jabber-mode-line-count-contacts)
+ (ad-activate 'jabber-send-presence)
+ (add-hook 'jabber-post-disconnect-hook
+ 'jabber-mode-line-presence-update)
+ (add-hook 'jabber-presence-hooks
+ 'jabber-mode-line-count-contacts))))
+
+(provide 'jabber-modeline)
+
+;;; arch-tag: c03a7d3b-8811-49d4-b0e0-7ffd661d7925
diff --git a/jabber-muc.el b/jabber-muc.el
new file mode 100644
index 0000000..b09fe15
--- /dev/null
+++ b/jabber-muc.el
@@ -0,0 +1,745 @@
+;; jabber-muc.el - advanced MUC functions
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-chat)
+(require 'jabber-widget)
+
+(require 'cl)
+
+(defvar *jabber-active-groupchats* nil
+ "alist of groupchats and nicknames
+Keys are strings, the bare JID of the room.
+Values are strings.")
+
+(defvar jabber-muc-participants nil
+ "alist of groupchats and participants
+Keys are strings, the bare JID of the room.
+Values are lists of nickname strings.")
+
+(defvar jabber-group nil
+ "the groupchat you are participating in")
+
+(defvar jabber-muc-topic ""
+ "The topic of the current MUC room.")
+
+(defcustom jabber-muc-default-nicknames nil
+ "Default nickname for specific MUC rooms."
+ :group 'jabber-chat
+ :type '(repeat
+ (cons :format "%v"
+ (string :tag "JID of room")
+ (string :tag "Nickname"))))
+
+(defcustom jabber-muc-autojoin nil
+ "List of MUC rooms to automatically join on connection."
+ :group 'jabber-chat
+ :type '(repeat (string :tag "JID of room")))
+
+(defcustom jabber-groupchat-buffer-format "*-jabber-groupchat-%n-*"
+ "The format specification for the name of groupchat buffers.
+
+These fields are available (all are about the group you are chatting
+in):
+
+%n Roster name of group, or JID if no nickname set
+%j Bare JID (without resource)"
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-groupchat-prompt-format "[%t] %n> "
+ "The format specification for lines in groupchat.
+
+These fields are available:
+
+%t Time, formatted according to `jabber-chat-time-format'
+%n, %u, %r
+ Nickname in groupchat
+%j Full JID (room@server/nick)"
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-muc-header-line-format
+ '(" " (:eval (jabber-jid-displayname jabber-group))
+ "\t" jabber-muc-topic)
+ "The specification for the header line of MUC buffers.
+
+The format is that of `mode-line-format' and `header-line-format'."
+ :type 'sexp
+ :group 'jabber-chat)
+
+(defcustom jabber-muc-private-buffer-format "*-jabber-muc-priv-%g-%n-*"
+ "The format specification for the buffer name for private MUC messages.
+
+These fields are available:
+
+%g Roster name of group, or JID if no nickname set
+%n Nickname of the group member you're chatting with"
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-muc-private-foreign-prompt-format "[%t] %g/%n> "
+ "The format specification for lines others type in a private MUC buffer.
+
+These fields are available:
+
+%t Time, formatted according to `jabber-chat-time-format'
+%n Nickname in room
+%g Short room name (either roster name or username part of JID)"
+ :type 'string
+ :group 'jabber-chat)
+
+(defcustom jabber-muc-private-header-line-format
+ '(" " (:eval (jabber-jid-resource jabber-chatting-with))
+ " in " (:eval (jabber-jid-displayname (jabber-jid-user jabber-chatting-with)))
+ "\t" jabber-events-message)
+ "The specification for the header line of private MUC chat buffers.
+
+The format is that of `mode-line-format' and `header-line-format'."
+ :type 'sexp
+ :group 'jabber-chat)
+
+(defvar jabber-muc-printers '(jabber-muc-snarf-topic)
+ "List of functions that may be able to print part of a MUC message.
+This gets prepended to `jabber-chat-printers', which see.")
+
+(defun jabber-muc-get-buffer (group)
+ "Return the chat buffer for chatroom GROUP.
+Either a string or a buffer is returned, so use `get-buffer' or
+`get-buffer-create'."
+ (format-spec jabber-groupchat-buffer-format
+ (list
+ (cons ?n (jabber-jid-displayname group))
+ (cons ?j (jabber-jid-user group)))))
+
+(defun jabber-muc-create-buffer (group)
+ "Prepare a buffer for chatroom GROUP.
+This function is idempotent."
+ (with-current-buffer (get-buffer-create (jabber-muc-get-buffer group))
+ (if (not (eq major-mode 'jabber-chat-mode)) (jabber-chat-mode))
+ (make-local-variable 'jabber-group)
+ (make-local-variable 'jabber-muc-topic)
+ (setq jabber-group group)
+ (setq jabber-send-function 'jabber-muc-send)
+ (setq header-line-format jabber-muc-header-line-format)
+ (current-buffer)))
+
+(defun jabber-muc-private-get-buffer (group nickname)
+ "Return the chat buffer for private chat with NICKNAME in GROUP.
+Either a string or a buffer is returned, so use `get-buffer' or
+`get-buffer-create'."
+ (format-spec jabber-muc-private-buffer-format
+ (list
+ (cons ?g (jabber-jid-displayname group))
+ (cons ?n nickname))))
+
+(defun jabber-muc-private-create-buffer (group nickname)
+ "Prepare a buffer for chatting with NICKNAME in GROUP.
+This function is idempotent."
+ (with-current-buffer (get-buffer-create (jabber-muc-private-get-buffer group nickname))
+ (if (not (eq major-mode 'jabber-chat-mode)) (jabber-chat-mode))
+ (make-local-variable 'jabber-chatting-with)
+ (setq jabber-chatting-with (concat group "/" nickname))
+ (setq jabber-send-function 'jabber-chat-send)
+ (setq header-line-format jabber-muc-private-header-line-format)
+
+ (current-buffer)))
+
+(defun jabber-muc-send (body)
+ "Send BODY to MUC room in current buffer."
+ ;; There is no need to display the sent message in the buffer, as
+ ;; we will get it back from the MUC server.
+ (jabber-send-sexp `(message
+ ((to . ,jabber-group)
+ (type . "groupchat"))
+ (body () ,(jabber-escape-xml body)))))
+
+(defun jabber-muc-add-groupchat (group nickname)
+ "Remember participating in GROUP under NICKNAME."
+ (let ((whichgroup (assoc group *jabber-active-groupchats*)))
+ (if whichgroup
+ (setcdr whichgroup nickname)
+ (add-to-list '*jabber-active-groupchats* (cons group nickname)))))
+
+(defun jabber-muc-remove-groupchat (group)
+ "Remove GROUP from internal bookkeeping."
+ (let ((whichgroup (assoc group *jabber-active-groupchats*))
+ (whichparticipants (assoc group jabber-muc-participants)))
+ (setq *jabber-active-groupchats*
+ (delq whichgroup *jabber-active-groupchats*))
+ (setq jabber-muc-participants
+ (delq whichparticipants jabber-muc-participants))))
+
+(defun jabber-muc-participant-plist (group nickname)
+ "Return plist associated with NICKNAME in GROUP.
+Return nil if nothing known about that combination."
+ (let ((whichparticipants (assoc group jabber-muc-participants)))
+ (when whichparticipants
+ (cdr (assoc nickname whichparticipants)))))
+
+(defun jabber-muc-modify-participant (group nickname new-plist)
+ "Assign properties in NEW-PLIST to NICKNAME in GROUP."
+ (let ((participants (assoc group jabber-muc-participants)))
+ ;; either we have a list of participants already...
+ (if participants
+ (let ((participant (assoc nickname participants)))
+ ;; and maybe this participant is already in the list
+ (if participant
+ ;; if so, just update role, affiliation, etc.
+ ;; XXX: calculate delta and report to user? e.g. "X was given voice"
+ (setf (cdr participant) new-plist)
+ (push (cons nickname new-plist) (cdr participants))))
+ ;; or we don't
+ (push (cons group (list (cons nickname new-plist))) jabber-muc-participants))))
+
+(defun jabber-muc-remove-participant (group nickname)
+ "Forget everything about NICKNAME in GROUP."
+ (let ((participants (assoc group jabber-muc-participants)))
+ (when participants
+ (let ((participant (assoc nickname (cdr participants))))
+ (setf (cdr participants) (delq participant (cdr participants)))))))
+
+(defun jabber-muc-read-completing (prompt)
+ "Read the name of a joined chatroom, or use chatroom of current buffer, if any."
+ (or jabber-group
+ (jabber-read-jid-completing prompt
+ (if (null *jabber-active-groupchats*)
+ (error "You haven't joined any group")
+ (mapcar (lambda (x) (jabber-jid-symbol (car x)))
+ *jabber-active-groupchats*))
+ t
+ jabber-group)))
+
+(defun jabber-muc-read-nickname (group prompt)
+ "Read the nickname of a participant in GROUP."
+ (let ((nicknames (cdr (assoc group jabber-muc-participants))))
+ (unless nicknames
+ (error "Unknown group: %s" group))
+ (completing-read prompt nicknames nil t)))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Configure groupchat" 'jabber-groupchat-get-config))
+(defun jabber-groupchat-get-config (group)
+ "Ask for MUC configuration form"
+ (interactive (list (jabber-muc-read-completing "Configure group: ")))
+ (jabber-send-iq group
+ "get"
+ '(query ((xmlns . "http://jabber.org/protocol/muc#owner")))
+ #'jabber-process-data #'jabber-groupchat-render-config
+ #'jabber-process-data "MUC configuration request failed"))
+
+(defun jabber-groupchat-render-config (xml-data)
+ "Render MUC configuration form"
+
+ (let ((query (jabber-iq-query xml-data))
+ xdata)
+ (dolist (x (jabber-xml-get-children query 'x))
+ (if (string= (jabber-xml-get-attribute x 'xmlns) "jabber:x:data")
+ (setq xdata x)))
+ (if (not xdata)
+ (insert "No configuration possible.\n")
+
+ (jabber-init-widget-buffer (jabber-xml-get-attribute xml-data 'from))
+
+ (jabber-render-xdata-form xdata)
+
+ (widget-create 'push-button :notify #'jabber-groupchat-submit-config "Submit")
+ (widget-insert "\t")
+ (widget-create 'push-button :notify #'jabber-groupchat-cancel-config "Cancel")
+ (widget-insert "\n")
+
+ (widget-setup)
+ (widget-minor-mode 1))))
+
+(defun jabber-groupchat-submit-config (&rest ignore)
+ "Submit MUC configuration form."
+
+ (jabber-send-iq jabber-submit-to
+ "set"
+ `(query ((xmlns . "http://jabber.org/protocol/muc#owner"))
+ ,(jabber-parse-xdata-form))
+ #'jabber-report-success "MUC configuration"
+ #'jabber-report-success "MUC configuration"))
+
+(defun jabber-groupchat-cancel-config (&rest ignore)
+ "Cancel MUC configuration form."
+
+ (jabber-send-iq jabber-submit-to
+ "set"
+ '(query ((xmlns . "http://jabber.org/protocol/muc#owner"))
+ (x ((xmlns . "jabber:x:data") (type . "cancel"))))
+ nil nil nil nil))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Join groupchat" 'jabber-groupchat-join))
+
+(defun jabber-groupchat-join (group nickname)
+ "join a groupchat, or change nick"
+ (interactive
+ (let ((group (jabber-read-jid-completing "group: ")))
+ (list group (jabber-muc-read-my-nickname group))))
+ ;; Remember that this is a groupchat _before_ sending the stanza.
+ ;; The response might come quicker than you think.
+ (let ((whichgroup (assoc group *jabber-active-groupchats*)))
+ (if whichgroup
+ (setcdr whichgroup nickname)
+ (add-to-list '*jabber-active-groupchats* (cons group nickname))))
+
+ (jabber-send-sexp `(presence ((to . ,(format "%s/%s" group nickname)))
+ (x ((xmlns . "http://jabber.org/protocol/muc")))))
+
+ (let ((buffer (jabber-muc-create-buffer group)))
+ ;; We don't want to switch to autojoined groupchats
+ (when (interactive-p)
+ (switch-to-buffer buffer))))
+
+(defun jabber-muc-read-my-nickname (group)
+ "Read nickname for joining GROUP."
+ (let ((default-nickname (or
+ (cdr (assoc group jabber-muc-default-nicknames))
+ jabber-nickname)))
+ (jabber-read-with-input-method (format "Nickname: (default %s) "
+ default-nickname)
+ nil nil default-nickname)))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Change nickname" 'jabber-muc-nick))
+
+(defalias 'jabber-muc-nick 'jabber-groupchat-join)
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Leave groupchat" 'jabber-groupchat-leave))
+
+(defun jabber-groupchat-leave (group)
+ "leave a groupchat"
+ (interactive (list (jabber-muc-read-completing "Leave which group: ")))
+ (let ((whichgroup (assoc group *jabber-active-groupchats*)))
+ ;; send unavailable presence to our own nick in room
+ (jabber-send-sexp `(presence ((to . ,(format "%s/%s" group (cdr whichgroup)))
+ (type . "unavailable"))))))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "List participants" 'jabber-muc-names))
+
+(defun jabber-muc-names (group)
+ "Print names, affiliations, and roles of participants in GROUP."
+ (interactive (list (jabber-muc-read-completing "Group: ")))
+ (with-current-buffer (jabber-muc-create-buffer group)
+ (let ((jabber-chat-fill-long-lines nil))
+ (jabber-chat-buffer-display 'jabber-muc-system-prompt nil
+ '(jabber-muc-print-names)
+ (cdr (assoc group jabber-muc-participants))))))
+
+(defun jabber-muc-print-names (participants)
+ "Format and insert data in PARTICIPANTS."
+ (apply 'insert "Participants:\n"
+ (format "%-15s %-15s %-11s %s\n" "Nickname" "Role" "Affiliation" "JID")
+ (mapcar (lambda (x)
+ (let ((plist (cdr x)))
+ (format "%-15s %-15s %-11s %s\n"
+ (car x)
+ (plist-get plist 'role)
+ (plist-get plist 'affiliation)
+ (or (plist-get plist 'jid) ""))))
+ participants)))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Set topic" 'jabber-muc-set-topic))
+
+(defun jabber-muc-set-topic (group topic)
+ "Set topic of GROUP to TOPIC."
+ (interactive
+ (let ((group (jabber-muc-read-completing "Group: ")))
+ (list group
+ (jabber-read-with-input-method "New topic: " jabber-muc-topic))))
+ (jabber-send-message group topic nil "groupchat"))
+
+(defun jabber-muc-snarf-topic (xml-data)
+ "Record subject (topic) of the given <message/>, if any."
+ (let ((new-topic (jabber-xml-path xml-data '(subject ""))))
+ (when new-topic
+ (setq jabber-muc-topic new-topic))))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Set role (kick, voice, op)" 'jabber-muc-set-role))
+
+(defun jabber-muc-set-role (group nickname role reason)
+ "Set role of NICKNAME in GROUP to ROLE, specifying REASON."
+ (interactive
+ (let* ((group (jabber-muc-read-completing "Group: "))
+ (nickname (jabber-muc-read-nickname group "Nickname: ")))
+ (list group nickname
+ (completing-read "New role: " '(("none") ("visitor") ("participant") ("moderator")) nil t)
+ (read-string "Reason: "))))
+ (unless (or (zerop (length nickname)) (zerop (length role)))
+ (jabber-send-iq group "set"
+ `(query ((xmlns . "http://jabber.org/protocol/muc#admin"))
+ (item ((nick . ,nickname)
+ (role . ,role))
+ ,(unless (zerop (length reason))
+ `(reason () ,reason))))
+ 'jabber-report-success "Role change"
+ 'jabber-report-success "Role change")))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Set affiliation (ban, member, admin)" 'jabber-muc-set-affiliation))
+
+(defun jabber-muc-set-affiliation (group nickname-or-jid nickname-p affiliation reason)
+ "Set affiliation of NICKNAME-OR-JID in GROUP to AFFILIATION.
+If NICKNAME-P is non-nil, NICKNAME-OR-JID is a nickname in the
+group, else it is a JID."
+ (interactive
+ (let ((group (jabber-muc-read-completing "Group: "))
+ (nickname-p (y-or-n-p "Specify user by room nickname? ")))
+ (list
+ group
+ (if nickname-p
+ (jabber-muc-read-nickname group "Nickname: ")
+ (jabber-read-jid-completing "User: "))
+ nickname-p
+ (completing-read "New affiliation: "
+ '(("none") ("outcast") ("member") ("admin") ("owner")) nil t)
+ (read-string "Reason: "))))
+ (let ((jid
+ (if nickname-p
+ (let ((participants (cdr (assoc group jabber-muc-participants))))
+ (unless participants
+ (error "Couldn't find group %s" group))
+ (let ((participant (cdr (assoc nickname-or-jid participants))))
+ (unless participant
+ (error "Couldn't find %s in group %s" nickname-or-jid group))
+ (or (plist-get participant 'jid)
+ (error "JID of %s in group %s is unknown" nickname-or-jid group))))
+ nickname-or-jid)))
+ (jabber-send-iq group "set"
+ `(query ((xmlns . "http://jabber.org/protocol/muc#admin"))
+ (item ((jid . ,jid)
+ (affiliation . ,affiliation))
+ ,(unless (zerop (length reason))
+ `(reason () ,reason))))
+ 'jabber-report-success "Affiliation change"
+ 'jabber-report-success "Affiliation change")))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Invite someone to chatroom" 'jabber-muc-invite))
+
+(defun jabber-muc-invite (jid group reason)
+ "Invite JID to GROUP, stating REASON."
+ (interactive
+ (list (jabber-read-jid-completing "Invite whom: ")
+ (jabber-muc-read-completing "To group: ")
+ (jabber-read-with-input-method "Reason: ")))
+ (jabber-send-sexp
+ `(message ((to . ,group))
+ (x ((xmlns . "http://jabber.org/protocol/muc#user"))
+ (invite ((to . ,jid))
+ ,(unless (zerop (length reason))
+ `(reason nil ,reason)))))))
+
+(add-to-list 'jabber-body-printers 'jabber-muc-print-invite)
+
+(defun jabber-muc-print-invite (xml-data)
+ "Print MUC invitation"
+ (dolist (x (jabber-xml-get-children xml-data 'x))
+ (when (string= (jabber-xml-get-attribute x 'xmlns) "http://jabber.org/protocol/muc#user")
+ (let ((invitation (car (jabber-xml-get-children x 'invite))))
+ (when invitation
+ (let ((group (jabber-xml-get-attribute xml-data 'from))
+ (inviter (jabber-xml-get-attribute invitation 'from))
+ (reason (car (jabber-xml-node-children (car (jabber-xml-get-children invitation 'reason))))))
+ ;; XXX: password
+ (insert "You have been invited to MUC room " (jabber-jid-displayname group))
+ (when inviter
+ (insert " by " (jabber-jid-displayname inviter)))
+ (insert ".")
+ (when reason
+ (insert " Reason: " reason))
+ (insert "\n\n")
+
+ (let ((action
+ `(lambda (&rest ignore) (interactive)
+ (jabber-groupchat-join ,group
+ (jabber-muc-read-my-nickname ,group)))))
+ (if (fboundp 'insert-button)
+ (insert-button "Accept"
+ 'action action)
+ ;; Simple button replacement
+ (let ((keymap (make-keymap)))
+ (define-key keymap "\r" action)
+ (insert (jabber-propertize "Accept"
+ 'keymap keymap
+ 'face 'highlight))))
+
+ (insert "\t")
+
+ (let ((action
+ `(lambda (&rest ignore) (interactive)
+ (let ((reason
+ (jabber-read-with-input-method
+ "Reason: ")))
+ (jabber-send-sexp
+ (list 'message
+ (list (cons 'to ,group))
+ (list 'x
+ (list (cons 'xmlns "http://jabber.org/protocol/muc#user"))
+ (list 'decline
+ (list (cons 'to ,inviter))
+ (unless (zerop (length reason))
+ (list 'reason nil reason))))))))))
+ (if (fboundp 'insert-button)
+ (insert-button "Decline"
+ 'action action)
+ ;; Simple button replacement
+ (let ((keymap (make-keymap)))
+ (define-key keymap "\r" action)
+ (insert (jabber-propertize "Decline"
+ 'keymap keymap
+ 'face 'highlight)))))))
+ (return t))))))
+
+(defun jabber-muc-autojoin ()
+ "Join rooms specified in variable `jabber-muc-autojoin'."
+ (interactive)
+ (dolist (group jabber-muc-autojoin)
+ (jabber-groupchat-join group (or
+ (cdr (assoc group jabber-muc-default-nicknames))
+ jabber-nickname))))
+
+(defun jabber-muc-message-p (message)
+ "Return non-nil if MESSAGE is a groupchat message.
+That does not include private messages in a groupchat."
+ ;; Public groupchat messages have type "groupchat" and are from
+ ;; room@server/nick. Public groupchat errors have type "error" and
+ ;; are from room@server.
+ (let ((from (jabber-xml-get-attribute message 'from))
+ (type (jabber-xml-get-attribute message 'type)))
+ (or
+ (string= type "groupchat")
+ (and (string= type "error")
+ (assoc from *jabber-active-groupchats*)))))
+
+(defun jabber-muc-sender-p (jid)
+ "Return non-nil if JID is a full JID of an MUC participant."
+ (and (assoc (jabber-jid-user jid) *jabber-active-groupchats*)
+ (jabber-jid-resource jid)))
+
+(defun jabber-muc-private-message-p (message)
+ "Return non-nil if MESSAGE is a private message in a groupchat."
+ (let ((from (jabber-xml-get-attribute message 'from))
+ (type (jabber-xml-get-attribute message 'type)))
+ (and
+ (not (string= type "groupchat"))
+ (jabber-muc-sender-p from))))
+
+(add-to-list 'jabber-jid-muc-menu
+ (cons "Open private chat" 'jabber-muc-private))
+
+(defun jabber-muc-private (group nickname)
+ "Open private chat with NICKNAME in GROUP."
+ (interactive
+ (let* ((group (jabber-muc-read-completing "Group: "))
+ (nickname (jabber-muc-read-nickname group "Nickname: ")))
+ (list group nickname)))
+ (switch-to-buffer (jabber-muc-private-create-buffer group nickname)))
+
+(defun jabber-muc-presence-p (presence)
+ "Return non-nil if PRESENCE is presence from groupchat."
+ (let ((from (jabber-xml-get-attribute presence 'from)))
+ (assoc (jabber-jid-user from) *jabber-active-groupchats*)))
+
+(defun jabber-muc-parse-affiliation (x-muc)
+ "Parse X-MUC in the muc#user namespace and return a plist.
+Return nil if X-MUC is nil."
+ ;; XXX: parse <actor/> and <reason/> tags? or maybe elsewhere?
+ (apply 'nconc (mapcar (lambda (prop) (list (car prop) (cdr prop)))
+ (jabber-xml-node-attributes
+ (car (jabber-xml-get-children x-muc 'item))))))
+
+(defun jabber-muc-print-prompt (xml-data)
+ "Print MUC prompt for message in XML-DATA."
+ (let ((nick (jabber-jid-resource (jabber-xml-get-attribute xml-data 'from)))
+ (timestamp (car (delq nil (mapcar 'jabber-x-delay (jabber-xml-get-children xml-data 'x))))))
+ (jabber-maybe-print-rare-time timestamp)
+ (if (stringp nick)
+ (insert (jabber-propertize
+ (format-spec jabber-groupchat-prompt-format
+ (list
+ (cons ?t (format-time-string
+ (if timestamp
+ jabber-chat-delayed-time-format
+ jabber-chat-time-format)
+ timestamp))
+ (cons ?n nick)
+ (cons ?u nick)
+ (cons ?r nick)
+ (cons ?j (concat jabber-group "/" nick))))
+ 'face 'jabber-chat-prompt-foreign
+ 'help-echo (concat (format-time-string "On %Y-%m-%d %H:%M:%S" timestamp) " from " nick " in " jabber-group)))
+ (jabber-muc-system-prompt))))
+
+(defun jabber-muc-private-print-prompt (xml-data)
+ "Print prompt for private MUC message in XML-DATA."
+ (let ((nick (jabber-jid-resource (jabber-xml-get-attribute xml-data 'from)))
+ (group (jabber-jid-user (jabber-xml-get-attribute xml-data 'from)))
+ (timestamp (car (delq nil (mapcar 'jabber-x-delay (jabber-xml-get-children xml-data 'x))))))
+ (jabber-maybe-print-rare-time timestamp)
+ (insert (jabber-propertize
+ (format-spec jabber-muc-private-foreign-prompt-format
+ (list
+ (cons ?t (format-time-string
+ (if timestamp
+ jabber-chat-delayed-time-format
+ jabber-chat-time-format)
+ timestamp))
+ (cons ?n nick)
+ (cons ?g (or (jabber-jid-rostername group)
+ (jabber-jid-username group)))))
+ 'face 'jabber-chat-prompt-foreign
+ 'help-echo (concat (format-time-string "On %Y-%m-%d %H:%M:%S" timestamp) " from " nick " in " jabber-group)))))
+
+(defun jabber-muc-system-prompt (&rest ignore)
+ "Print system prompt for MUC."
+ (jabber-maybe-print-rare-time nil)
+ (insert (jabber-propertize
+ (format-spec jabber-groupchat-prompt-format
+ (list
+ (cons ?t (format-time-string jabber-chat-time-format))
+ (cons ?n "")
+ (cons ?u "")
+ (cons ?r "")
+ (cons ?j jabber-group)))
+ 'face 'jabber-chat-prompt-system
+ 'help-echo (format-time-string "System message on %Y-%m-%d %H:%M:%S"))))
+
+(add-to-list 'jabber-message-chain 'jabber-muc-process-message)
+
+(defun jabber-muc-process-message (xml-data)
+ "If XML-DATA is a groupchat message, handle it as such."
+ (when (jabber-muc-message-p xml-data)
+ (let* ((from (jabber-xml-get-attribute xml-data 'from))
+ (group (jabber-jid-user from))
+ (nick (jabber-jid-resource from))
+ (error-p (jabber-xml-get-children xml-data 'error))
+ (body-text (car (jabber-xml-node-children
+ (car (jabber-xml-get-children
+ xml-data 'body))))))
+ (with-current-buffer (jabber-muc-create-buffer group)
+ ;; Call alert hooks only when something is output
+ (when
+ (jabber-chat-buffer-display 'jabber-muc-print-prompt
+ xml-data
+ (if error-p
+ '(jabber-chat-print-error)
+ (append jabber-muc-printers
+ jabber-chat-printers))
+ xml-data)
+
+ (dolist (hook '(jabber-muc-hooks jabber-alert-muc-hooks))
+ (run-hook-with-args hook
+ nick group (current-buffer) body-text
+ (funcall jabber-alert-muc-function
+ nick group (current-buffer) body-text))))))))
+
+(defun jabber-muc-process-presence (presence)
+ (let* ((from (jabber-xml-get-attribute presence 'from))
+ (type (jabber-xml-get-attribute presence 'type))
+ (x-muc (find-if
+ (lambda (x) (equal (jabber-xml-get-attribute x 'xmlns)
+ "http://jabber.org/protocol/muc#user"))
+ (jabber-xml-get-children presence 'x)))
+ (group (jabber-jid-user from))
+ (nickname (jabber-jid-resource from))
+ (symbol (jabber-jid-symbol from))
+ (item (car (jabber-xml-get-children x-muc 'item)))
+ (actor (jabber-xml-get-attribute (car (jabber-xml-get-children item 'actor)) 'jid))
+ (reason (car (jabber-xml-node-children (car (jabber-xml-get-children item 'reason)))))
+ (status-code (jabber-xml-get-attribute
+ (car (jabber-xml-get-children x-muc 'status))
+ 'code))
+ (error-node (car (jabber-xml-get-children presence 'error))))
+ ;; handle leaving a room
+ (cond
+ ((or (string= type "unavailable") (string= type "error"))
+ ;; are we leaving?
+ (if (string= nickname (cdr (assoc group *jabber-active-groupchats*)))
+ (progn
+ (jabber-muc-remove-groupchat group)
+ ;; If there is no buffer for this groupchat, don't bother
+ ;; creating one just to tell that user left the room.
+ (let ((buffer (get-buffer (jabber-muc-get-buffer group))))
+ (when buffer
+ (with-current-buffer buffer
+ (jabber-chat-buffer-display
+ 'jabber-muc-system-prompt
+ nil
+ '(insert)
+ (cond
+ ((string= type "error")
+ (jabber-propertize
+ (concat "Error entering room"
+ (when error-node
+ (concat ": " (jabber-parse-error error-node))))
+ 'face 'jabber-chat-error))
+ ((equal status-code "301")
+ (concat "You have been banned"
+ (when actor (concat " by " actor))
+ (when reason (concat " - '" reason "'"))))
+ ((equal status-code "307")
+ (concat "You have been kicked"
+ (when actor (concat " by " actor))
+ (when reason (concat " - '" reason "'"))))
+ (t
+ "You have left the chatroom")))))))
+ ;; or someone else?
+ (jabber-muc-remove-participant group nickname)
+ (with-current-buffer (jabber-muc-create-buffer group)
+ (jabber-chat-buffer-display
+ 'jabber-muc-system-prompt
+ nil
+ '(insert)
+ (cond
+ ((equal status-code "301")
+ (concat nickname " has been banned"
+ (when actor (concat " by " actor))
+ (when reason (concat " - '" reason "'"))))
+ ((equal status-code "307")
+ (concat nickname " has been kicked"
+ (when actor (concat " by " actor))
+ (when reason (concat " - '" reason "'"))))
+ ((equal status-code "303")
+ (concat nickname " changes nickname to "
+ (jabber-xml-get-attribute item 'nick)))
+ (t
+ (concat nickname " has left the chatroom")))))))
+ (t
+ ;; someone is entering
+ (let ((new-participant (not (jabber-muc-participant-plist group nickname)))
+ (new-plist (jabber-muc-parse-affiliation x-muc)))
+ (jabber-muc-modify-participant group nickname new-plist)
+ (when new-participant
+ (with-current-buffer (jabber-muc-create-buffer group)
+ (jabber-chat-buffer-display 'jabber-muc-system-prompt
+ nil
+ '(insert)
+ (format "%s enters the chatroom" nickname)))))))))
+
+(provide 'jabber-muc)
+
+;;; arch-tag: 1ff7ab35-1717-46ae-b803-6f5b3fb2cd7d
diff --git a/jabber-presence.el b/jabber-presence.el
new file mode 100644
index 0000000..a08e680
--- /dev/null
+++ b/jabber-presence.el
@@ -0,0 +1,346 @@
+;; jabber-presence.el - roster and presence bookkeeping
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-core)
+(require 'jabber-iq)
+(require 'jabber-alert)
+(require 'jabber-util)
+(require 'jabber-menu)
+(require 'jabber-muc)
+
+(add-to-list 'jabber-iq-set-xmlns-alist
+ (cons "jabber:iq:roster" (function (lambda (x) (jabber-process-roster x nil)))))
+(defun jabber-process-roster (xml-data closure-data)
+ "process an incoming roster infoquery result
+CLOSURE-DATA should be 'initial if initial roster push, nil otherwise."
+
+ ;; Perform sanity check on "from" attribute: it should be either absent
+ ;; or match our own JID.
+ (let ((from (jabber-xml-get-attribute xml-data 'from))
+ (type (jabber-xml-get-attribute xml-data 'type))
+ (id (jabber-xml-get-attribute xml-data 'id)))
+ (if (not (or (null from)
+ (string= from (concat jabber-username "@" jabber-server))
+ (string= from (concat jabber-username "@" jabber-server "/" jabber-resource))))
+ (message "Roster push with invalid \"from\": \"%s\"" from)
+
+ ;; If *jabber-roster* is empty, we just fill up the roster with
+ ;; the given data. If not, we have received a partial roster
+ ;; update, so just fill in that data. These cases can be
+ ;; differentiated by the type attribute of the iq tag: if
+ ;; type='result', we asked for the whole roster. If type='set',
+ ;; we are getting a "roster push".
+ (dolist (item (jabber-xml-get-children (car (jabber-xml-get-children xml-data 'query)) 'item))
+ (let (roster-item
+ (jid (jabber-jid-symbol (jabber-xml-get-attribute item 'jid))))
+
+ ;; Find contact if already in roster
+ (setq roster-item (car (memq jid *jabber-roster*)))
+
+ ;; If not found, create a new roster item.
+ (when (null roster-item)
+ (message "%s added to roster" jid)
+ (setq roster-item jid)
+ (setq *jabber-roster* (cons roster-item *jabber-roster*)))
+
+ ;; Now, get all data associated with the contact.
+ (put roster-item 'name (jabber-xml-get-attribute item 'name))
+ (put roster-item 'subscription (jabber-xml-get-attribute item 'subscription))
+ (put roster-item 'ask (jabber-xml-get-attribute item 'ask))
+
+ ;; Since roster items can't be changed incrementally, we
+ ;; save the original XML to be able to modify it, instead of
+ ;; having to reproduce it. This is for forwards
+ ;; compatibility.
+ (put roster-item 'xml item)
+
+ ;; xml-parse-tag will put "" as the only child element of an
+ ;; empty element, (e.g. <item jid="foo@bar"/> as opposed to
+ ;; <item jid="foo@bar"><group>baz</group></item>) which
+ ;; xml-get-children subsequently will choke on. We want to
+ ;; avoid that with an extra check.
+ (put roster-item 'groups (mapcar #'(lambda (foo) (nth 2 foo)) (jabber-xml-get-children item 'group)))
+
+ ;; If subscripton="remove", contact is to be removed from roster
+ (when (string= (get roster-item 'subscription) "remove")
+ (message "%s removed from roster" jid)
+ (setq *jabber-roster* (delq roster-item *jabber-roster*)))
+
+ )))
+ (jabber-display-roster)
+ (if (and id (string= type "set"))
+ (jabber-send-iq jabber-server "result" nil
+ nil nil nil nil id))))
+
+(add-to-list 'jabber-presence-chain 'jabber-process-presence)
+(defun jabber-process-presence (xml-data)
+ "process incoming presence tags"
+ (let ((from (jabber-xml-get-attribute xml-data 'from))
+ (to (jabber-xml-get-attribute xml-data 'to))
+ (type (jabber-xml-get-attribute xml-data 'type))
+ (presence-show (car (jabber-xml-node-children
+ (car (jabber-xml-get-children xml-data 'show)))))
+ (presence-status (car (jabber-xml-node-children
+ (car (jabber-xml-get-children xml-data 'status)))))
+ (error (car (jabber-xml-get-children xml-data 'error)))
+ (priority (string-to-number (or (car (jabber-xml-node-children (car (jabber-xml-get-children xml-data 'priority))))
+ "0"))))
+ (cond
+ ((string= type "subscribe")
+ (run-with-idle-timer 0.01 nil #'jabber-process-subscription-request from presence-status))
+
+ ((jabber-muc-presence-p xml-data)
+ (jabber-muc-process-presence xml-data))
+
+ (t
+ ;; XXX: Think about what to do about out-of-roster presences.
+ (let ((buddy (jabber-jid-symbol from)))
+ (if (memq buddy *jabber-roster*)
+ (let* ((oldstatus (get buddy 'show))
+ (resource (or (jabber-jid-resource from) ""))
+ (resource-plist (cdr (assoc resource
+ (get buddy 'resources))))
+ newstatus)
+ (cond
+ ((string= type "unavailable")
+ (setq resource-plist
+ (plist-put resource-plist 'connected nil))
+ (setq resource-plist
+ (plist-put resource-plist 'show nil))
+ (setq resource-plist
+ (plist-put resource-plist 'status
+ (jabber-unescape-xml presence-status))))
+
+ ((string= type "error")
+ (setq newstatus "error")
+ (setq resource-plist
+ (plist-put resource-plist 'connected nil))
+ (setq resource-plist
+ (plist-put resource-plist 'show "error"))
+ (setq resource-plist
+ (plist-put resource-plist 'status
+ (if error
+ (jabber-parse-error error)
+ (jabber-unescape-xml presence-status)))))
+ ((or
+ (string= type "unsubscribe")
+ (string= type "subscribed")
+ (string= type "unsubscribed"))
+ ;; Do nothing, except letting the user know. The Jabber protocol
+ ;; places all this complexity on the server.
+ (setq newstatus type))
+ (t
+ (setq resource-plist
+ (plist-put resource-plist 'connected t))
+ (setq resource-plist
+ (plist-put resource-plist 'show (or presence-show "")))
+ (setq resource-plist
+ (plist-put resource-plist 'status
+ (jabber-unescape-xml presence-status)))
+ (setq resource-plist
+ (plist-put resource-plist 'priority priority))
+ (setq newstatus (or presence-show ""))))
+
+ ;; this is for `assoc-set!' in guile
+ (if (assoc resource (get buddy 'resources))
+ (setcdr (assoc resource (get buddy 'resources)) resource-plist)
+ (put buddy 'resources (cons (cons resource resource-plist) (get buddy 'resources))))
+ (jabber-prioritize-resources buddy)
+
+ (dolist (hook '(jabber-presence-hooks jabber-alert-presence-hooks))
+ (run-hook-with-args hook
+ buddy
+ oldstatus
+ newstatus
+ (jabber-unescape-xml
+ (plist-get resource-plist 'status))
+ (funcall jabber-alert-presence-message-function
+ buddy
+ oldstatus
+ newstatus
+ (jabber-unescape-xml
+ (plist-get resource-plist 'status))))))))))))
+
+(defun jabber-process-subscription-request (from presence-status)
+ "process an incoming subscription request"
+ (dolist (hook '(jabber-presence-hooks jabber-alert-presence-hooks))
+ (run-hook-with-args hook (jabber-jid-symbol from) nil "subscribe" presence-status (funcall jabber-alert-presence-message-function (jabber-jid-symbol from) nil "subscribe" presence-status)))
+ (jabber-send-sexp
+ (list 'presence
+ (list (cons 'to from)
+ (cons 'type
+ (if (yes-or-no-p (format "the user - %s - has requested to subscribe to your presence (%s). allow? "
+ (jabber-jid-displayname from)
+ (jabber-unescape-xml presence-status)))
+ "subscribed"
+ "unsubscribed")))))
+ (when (yes-or-no-p (format "Do you want to subscribe to %s's presence? " from))
+ (jabber-send-sexp
+ (list 'presence (list (cons 'to from)
+ (cons 'type "subscribe"))))))
+
+(defun jabber-prioritize-resources (buddy)
+ "Set connected, show and status properties for BUDDY from highest-priority resource."
+ (let ((resource-alist (get buddy 'resources))
+ (highest-priority nil))
+ ;; Reset to nil at first, for cases (a) resource-alist is nil
+ ;; and (b) all resources are disconnected.
+ (put buddy 'connected nil)
+ (put buddy 'show nil)
+ (put buddy 'status nil)
+ (mapc #'(lambda (resource)
+ (let* ((resource-plist (cdr resource))
+ (priority (plist-get resource-plist 'priority)))
+ (if (plist-get resource-plist 'connected)
+ (when (or (null highest-priority)
+ (and priority
+ (> priority highest-priority)))
+ ;; if no priority specified, interpret as zero
+ (setq highest-priority (or priority 0))
+ (put buddy 'connected (plist-get resource-plist 'connected))
+ (put buddy 'show (plist-get resource-plist 'show))
+ (put buddy 'status (plist-get resource-plist 'status))
+ (put buddy 'resource (car resource)))
+
+ ;; if we have not found a connected resource yet, but this
+ ;; disconnected resource has a status message, display it.
+ (when (not (get buddy 'connected))
+ (if (plist-get resource-plist 'status)
+ (put buddy 'status (plist-get resource-plist 'status)))
+ (if (plist-get resource-plist 'show)
+ (put buddy 'show (plist-get resource-plist 'show)))))))
+ resource-alist)))
+
+(defun jabber-count-connected-resources (buddy)
+ "Return the number of connected resources for BUDDY."
+ (let ((resource-alist (get buddy 'resources))
+ (count 0))
+ (dolist (resource resource-alist)
+ (if (plist-get (cdr resource) 'connected)
+ (setq count (1+ count))))
+ count))
+
+(defun jabber-send-presence (show status priority)
+ "send a presence tag to the server"
+ (interactive (list (completing-read "show:"
+ '(("" . nil)
+ ("away" . nil)
+ ("xa" . nil)
+ ("dnd" . nil)
+ ("chat" . nil))
+ nil t)
+ (jabber-read-with-input-method "status message: " *jabber-current-status* '*jabber-status-history*)
+ (read-string "priority: " (progn
+ (unless *jabber-current-priority*
+ (setq *jabber-current-priority*
+ jabber-default-priority))
+ (int-to-string *jabber-current-priority*)))))
+ (if (numberp priority)
+ (setq priority (int-to-string priority)))
+ (setq *jabber-current-status* status)
+ (setq *jabber-current-show* show)
+ (setq *jabber-current-priority* (string-to-int priority))
+ (jabber-send-sexp `(presence ()
+ ,(if (> (length status) 0)
+ `(status () ,(jabber-escape-xml status)))
+ ,(if (> (length show) 0)
+ `(show () ,(jabber-escape-xml show)))
+ (priority () ,(jabber-escape-xml (int-to-string *jabber-current-priority*)))))
+ (jabber-display-roster))
+
+(defun jabber-send-away-presence ()
+ "Set status to away.
+Status description is empty. Priority is unchanged."
+ (interactive)
+ (jabber-send-presence "away" "" *jabber-current-priority*))
+
+(defun jabber-send-xa-presence ()
+ "Send extended away presence.
+Status description is empty. Priority is unchanged."
+ (interactive)
+ (jabber-send-presence "xa" "" *jabber-current-priority*))
+
+(defun jabber-send-default-presence ()
+ "Send default presence.
+Default presence is specified by `jabber-default-priority', `jabber-default-show',
+and `jabber-default-status'."
+ (interactive)
+ (jabber-send-presence jabber-default-show jabber-default-status jabber-default-priority))
+
+(add-to-list 'jabber-jid-roster-menu
+ (cons "Send subscription request" 'jabber-send-subscription-request))
+(defun jabber-send-subscription-request (to &optional request)
+ "send a subscription request to jid, showing him your request text, if specified"
+ (interactive (list (jabber-read-jid-completing "to: ")
+ (jabber-read-with-input-method "request: ")))
+ (jabber-send-sexp `(presence ((to . ,to)
+ (type . "subscribe"))
+ ,(if (and request (> (length request) 0))
+ request))))
+
+(add-to-list 'jabber-jid-roster-menu
+ (cons "Add/modify roster entry" 'jabber-roster-change))
+(defun jabber-roster-change (jid name groups)
+ "Add or change a roster item."
+ (interactive (let* ((jid (jabber-jid-symbol
+ (jabber-read-jid-completing "Add/change JID: ")))
+ (name (get jid 'name))
+ (groups (get jid 'groups)))
+ (list jid (jabber-read-with-input-method (format "Name: (default `%s') " name) nil nil name)
+ (car (read-from-string (jabber-read-with-input-method (format "Groups: (default `%S') " groups) nil nil (format "%S" groups)))))))
+ ;; If new fields are added to the roster XML structure in a future standard,
+ ;; they will be clobbered by this function.
+ (jabber-send-iq nil "set"
+ (list 'query (list (cons 'xmlns "jabber:iq:roster"))
+ (list 'item (append
+ (list (cons 'jid (symbol-name jid)))
+ (if (and name (> (length name) 0))
+ (list (cons 'name name))))
+ (mapcar #'(lambda (x) `(group () ,x))
+ groups)))
+ #'jabber-report-success "Roster item change"
+ #'jabber-report-success "Roster item change"))
+
+(add-to-list 'jabber-jid-roster-menu
+ (cons "Delete roster entry" 'jabber-roster-delete))
+(defun jabber-roster-delete (jid)
+ (interactive (list (jabber-read-jid-completing "Delete from roster: ")))
+ (jabber-send-iq nil "set"
+ `(query ((xmlns . "jabber:iq:roster"))
+ (item ((jid . ,jid)
+ (subscription . "remove"))))
+ #'jabber-report-success "Roster item removal"
+ #'jabber-report-success "Roster item removal"))
+
+(defun jabber-roster-delete-jid-at-point ()
+ "Delete JID at point from roster.
+Signal an error if there is no JID at point."
+ (interactive)
+ (let ((jid-at-point (get-text-property (point)
+ 'jabber-jid)))
+ (if (and jid-at-point
+ (yes-or-no-p (format "Really delete %s from roster? " jid-at-point)))
+ (jabber-roster-delete jid-at-point)
+ (error "No contact at point"))))
+
+(provide 'jabber-presence)
+
+;;; arch-tag: b8616d4c-dde8-423e-86c7-da7b4928afc3
diff --git a/jabber-ratpoison.el b/jabber-ratpoison.el
new file mode 100644
index 0000000..141f290
--- /dev/null
+++ b/jabber-ratpoison.el
@@ -0,0 +1,34 @@
+;; jabber-ratpoison.el - emacs-jabber interface to ratpoison
+
+;; Copyright (C) 2005 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(defun jabber-ratpoison-message (msg)
+ "Show MSG in Ratpoison"
+ ;; Possible errors include not finding the ratpoison binary, and
+ ;; too many pipes open because of message flood.
+ (condition-case e
+ (let ((process-connection-type))
+ (start-process "ratpoison" nil "ratpoison" "-c" (concat "echo " msg)))
+ (error nil)))
+
+(define-jabber-alert ratpoison "Show a message through the Ratpoison window manager"
+ 'jabber-ratpoison-message)
+
+(provide 'jabber-ratpoison)
+;; arch-tag: 19650075-5D05-11D9-B80F-000A95C2FCD0
diff --git a/jabber-register.el b/jabber-register.el
new file mode 100644
index 0000000..4acdea2
--- /dev/null
+++ b/jabber-register.el
@@ -0,0 +1,143 @@
+;; jabber-register.el - registration according to JEP-0077
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-iq)
+(require 'jabber-widget)
+
+(add-to-list 'jabber-jid-service-menu
+ (cons "Register with service" 'jabber-get-register))
+(defun jabber-get-register (to)
+ "Send IQ get request in namespace \"jabber:iq:register\"."
+ (interactive (list (jabber-read-jid-completing "Register with: ")))
+ (jabber-send-iq to
+ "get"
+ '(query ((xmlns . "jabber:iq:register")))
+ #'jabber-process-data #'jabber-process-register-or-search
+ #'jabber-report-success "Registration"))
+
+(defun jabber-process-register-or-search (xml-data)
+ "Display results from jabber:iq:{register,search} query as a form."
+
+ (let ((query (jabber-iq-query xml-data))
+ (have-xdata nil)
+ (type (cond
+ ((string= (jabber-iq-xmlns xml-data) "jabber:iq:register")
+ 'register)
+ ((string= (jabber-iq-xmlns xml-data) "jabber:iq:search")
+ 'search)
+ (t
+ (error "Namespace %s not handled by jabber-process-register-or-search" (jabber-iq-xmlns xml-data))))))
+
+ (cond
+ ((eq type 'register)
+ ;; If there is no `from' attribute, we are registering with the server
+ (jabber-init-widget-buffer (or (jabber-xml-get-attribute xml-data 'from) jabber-server)))
+
+ ((eq type 'search)
+ ;; no such thing here
+ (jabber-init-widget-buffer (jabber-xml-get-attribute xml-data 'from))))
+
+ (widget-insert (if (eq type 'register) "Register with " "Search ") jabber-submit-to "\n\n")
+ (when (and (eq type 'register)
+ jabber-register-p)
+ (widget-insert "Don't change the username here unless you also change ")
+ (widget-create 'link
+ :notify (lambda (&rest ignore)
+ (customize-variable 'jabber-username))
+ "jabber-username")
+ (widget-insert ".\n\n"))
+
+ (dolist (x (jabber-xml-get-children query 'x))
+ (when (string= (jabber-xml-get-attribute x 'xmlns) "jabber:x:data")
+ (setq have-xdata t)
+ ;; If the registration form obeys JEP-0068, we know
+ ;; for sure how to put a default username in it.
+ (jabber-render-xdata-form x
+ (if (and jabber-register-p
+ (string= (jabber-xdata-formtype x) "jabber:iq:register"))
+ (list (cons "username" jabber-username))
+ nil))))
+ (if (not have-xdata)
+ (jabber-render-register-form query))
+
+ (widget-create 'push-button :notify (if (eq type 'register)
+ #'jabber-submit-register
+ #'jabber-submit-search) "Submit")
+ (when (eq type 'register)
+ (widget-insert "\t")
+ (widget-create 'push-button :notify #'jabber-remove-register "Cancel registration"))
+ (widget-insert "\n")
+ (widget-setup)
+ (widget-minor-mode 1)))
+
+(defun jabber-submit-register (&rest ignore)
+ "Submit registration input. See `jabber-process-register-or-search'."
+
+ (let ((handler (if jabber-register-p
+ #'jabber-process-register-secondtime
+ #'jabber-report-success))
+ (text (concat "Registration with " jabber-submit-to)))
+ (jabber-send-iq jabber-submit-to
+ "set"
+
+ (cond
+ ((eq jabber-form-type 'register)
+ `(query ((xmlns . "jabber:iq:register"))
+ ,@(jabber-parse-register-form)))
+ ((eq jabber-form-type 'xdata)
+ `(query ((xmlns . "jabber:iq:register"))
+ ,(jabber-parse-xdata-form)))
+ (t
+ (error "Unknown form type: %s" jabber-form-type)))
+ handler (if jabber-register-p 'success text)
+ handler (if jabber-register-p 'failure text)))
+
+ (message "Registration sent"))
+
+(defun jabber-process-register-secondtime (xml-data closure-data)
+ "Receive registration success or failure.
+CLOSURE-DATA is either 'success or 'error."
+ (setq jabber-register-p nil)
+ (cond
+ ((eq closure-data 'success)
+ (message "Registration successful. Your JID is %s@%s."
+ jabber-username jabber-server)
+ (sit-for 3)
+ (jabber-get-auth jabber-server))
+ (t
+ (jabber-report-success xml-data "Account registration")
+ (sit-for 3)
+ (jabber-disconnect))))
+
+(defun jabber-remove-register (&rest ignore)
+ "Cancel registration. See `jabber-process-register-or-search'."
+
+ (if (yes-or-no-p (concat "Are you sure that you want to cancel your registration to " jabber-submit-to "? "))
+ (jabber-send-iq jabber-submit-to
+ "set"
+ '(query ((xmlns . "jabber:iq:register"))
+ (remove))
+ #'jabber-report-success "Unregistration"
+ #'jabber-report-success "Unregistration")))
+
+(provide 'jabber-register)
+
+;;; arch-tag: e6b349d6-b1ad-4d19-a412-74459dfae239
diff --git a/jabber-roster.el b/jabber-roster.el
new file mode 100644
index 0000000..1cd151d
--- /dev/null
+++ b/jabber-roster.el
@@ -0,0 +1,456 @@
+;; jabber-roster.el - displaying the roster -*- coding: utf-8; -*-
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-presence)
+(require 'jabber-util)
+(require 'jabber-alert)
+(require 'jabber-keymap)
+(require 'format-spec)
+
+(defgroup jabber-roster nil "roster display options"
+ :group 'jabber)
+
+(defcustom jabber-roster-line-format " %c %-25n %u %-8s %S\n"
+ "The format specification of the lines in the roster display.
+
+These fields are available:
+
+%c \"*\" if the contact is connected, or \" \" if not
+%u sUbscription state - see below
+%n Nickname of contact, or JID if no nickname
+%j Bare JID of contact (without resource)
+%r Highest-priority resource of contact
+%s Availability of contact as string (\"Online\", \"Away\" etc)
+%S Status string specified by contact
+
+%u is replaced by one of the strings given by
+`jabber-roster-subscription-display'."
+ :type 'string
+ :group 'jabber-roster)
+
+(defcustom jabber-roster-subscription-display '(("none" . " ")
+ ("from" . "< ")
+ ("to" . " >")
+ ("both" . "<->"))
+ "Strings used for indicating subscription status of contacts.
+\"none\" means that there is no subscription between you and the
+contact.
+\"from\" means that the contact has a subscription to you, but you
+have no subscription to the contact.
+\"to\" means that you have a subscription to the contact, but the
+contact has no subscription to you.
+\"both\" means a mutual subscription.
+
+Having a \"presence subscription\" means being able to see the
+other person's presence.
+
+Some fancy arrows you might want to use, if your system can
+display them: ← → ⇄ ↔"
+ :type '(list (cons :format "%v" (const :format "" "none") (string :tag "None"))
+ (cons :format "%v" (const :format "" "from") (string :tag "From"))
+ (cons :format "%v" (const :format "" "to") (string :tag "To"))
+ (cons :format "%v" (const :format "" "both") (string :tag "Both")))
+ :group 'jabber-roster)
+
+(defcustom jabber-resource-line-format " %r - %s (%S), priority %p\n"
+ "The format specification of resource lines in the roster display.
+These are displayed when `jabber-show-resources' permits it.
+
+These fields are available:
+
+%c \"*\" if the contact is connected, or \" \" if not
+%n Nickname of contact, or JID if no nickname
+%j Bare JID of contact (without resource)
+%p Priority of this resource
+%r Name of this resource
+%s Availability of resource as string (\"Online\", \"Away\" etc)
+%S Status string specified by resource"
+ :type 'string
+ :group 'jabber-roster)
+
+(defcustom jabber-roster-sort-functions
+ '(jabber-roster-sort-by-status jabber-roster-sort-by-displayname)
+ "Sort roster according to these criteria.
+
+These functions should take two roster items A and B, and return:
+<0 if A < B
+0 if A = B
+>0 if A > B"
+ :type 'hook
+ :options '(jabber-roster-sort-by-status
+ jabber-roster-sort-by-displayname)
+ :group 'jabber-roster)
+
+(defcustom jabber-sort-order '("chat" "" "away" "dnd" "xa")
+ "Sort by status in this order. Anything not in list goes last.
+Offline is represented as nil."
+ :type '(repeat (restricted-sexp :match-alternatives (stringp nil)))
+ :group 'jabber-roster)
+
+(defcustom jabber-show-resources 'sometimes
+ "Show resources in roster?"
+ :type '(radio (const :tag "Never" nil)
+ (const :tag "When more than one connected resource" sometimes)
+ (const :tag "Always" always))
+ :group 'jabber-roster)
+
+(defcustom jabber-remove-newlines t
+ "Remove newlines in status messages?
+Newlines in status messages mess up the roster display. However,
+they are essential to status message poets. Therefore, you get to
+choose the behaviour.
+
+Trailing newlines are always removed, regardless of this variable."
+ :type 'boolean
+ :group 'jabber-roster)
+
+(defcustom jabber-roster-show-bindings t
+ "Show keybindings in roster buffer?"
+ :type 'boolean
+ :group 'jabber-roster)
+
+(defcustom jabber-roster-mode-hook nil
+ "Hook run when entering Roster mode."
+ :group 'jabber-roster
+ :type 'hook)
+
+(defface jabber-roster-user-online
+ '((t (:foreground "blue" :weight bold :slant normal)))
+ "face for displaying online users"
+ :group 'jabber-roster)
+
+(defface jabber-roster-user-xa
+ '((((background dark)) (:foreground "magenta" :weight normal :slant italic))
+ (t (:foreground "black" :weight normal :slant italic)))
+ "face for displaying extended away users"
+ :group 'jabber-roster)
+
+(defface jabber-roster-user-dnd
+ '((t (:foreground "red" :weight normal :slant italic)))
+ "face for displaying do not disturb users"
+ :group 'jabber-roster)
+
+(defface jabber-roster-user-away
+ '((t (:foreground "dark green" :weight normal :slant italic)))
+ "face for displaying away users"
+ :group 'jabber-roster)
+
+(defface jabber-roster-user-chatty
+ '((t (:foreground "dark orange" :weight bold :slant normal)))
+ "face for displaying chatty users"
+ :group 'jabber-roster)
+
+(defface jabber-roster-user-error
+ '((t (:foreground "red" :weight light :slant italic)))
+ "face for displaying users sending presence errors"
+ :group 'jabber-roster)
+
+(defface jabber-roster-user-offline
+ '((t (:foreground "dark grey" :weight light :slant italic)))
+ "face for displaying offline users"
+ :group 'jabber-roster)
+
+(defvar jabber-roster-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map jabber-common-keymap)
+ (define-key map [mouse-2] 'jabber-popup-combined-menu)
+ (define-key map (kbd "TAB") 'jabber-go-to-next-jid)
+ (define-key map (kbd "RET") 'jabber-chat-with-jid-at-point)
+ (define-key map (kbd "C-k") 'jabber-roster-delete-jid-at-point)
+
+ (define-key map "e" 'jabber-roster-change)
+ (define-key map "s" 'jabber-send-subscription-request)
+ (define-key map "q" 'bury-buffer)
+ (define-key map "i" 'jabber-get-disco-items)
+ (define-key map "j" 'jabber-groupchat-join)
+ (define-key map "I" 'jabber-get-disco-info)
+ (define-key map "b" 'jabber-get-browse)
+ (define-key map "v" 'jabber-get-version)
+ (define-key map "a" 'jabber-send-presence)
+ (define-key map "g" 'jabber-display-roster)
+ ;;(define-key map "D" 'jabber-disconnect)
+ map))
+
+(defun jabber-roster-mode ()
+ "Major mode for Jabber roster display.
+Use the keybindings (mnemonic as Chat, Roster, Info, MUC, Service) to
+bring up menus of actions.
+\\{jabber-roster-mode-map}"
+ (kill-all-local-variables)
+ (setq major-mode 'jabber-roster-mode
+ mode-name "jabber-roster")
+ (use-local-map jabber-roster-mode-map)
+ (setq buffer-read-only t)
+ (if (fboundp 'run-mode-hooks)
+ (run-mode-hooks 'jabber-roster-mode-hook)
+ (run-hooks 'jabber-roster-mode-hook)))
+
+(put 'jabber-roster-mode 'mode-class 'special)
+
+(defun jabber-switch-to-roster-buffer ()
+ "Switch to roster buffer."
+ (interactive)
+ (switch-to-buffer jabber-roster-buffer))
+
+(defun jabber-sort-roster ()
+ "sort roster according to online status"
+ (setq *jabber-roster*
+ (sort *jabber-roster* #'jabber-roster-sort-items)))
+
+(defun jabber-roster-sort-items (a b)
+ "Sort roster items A and B according to `jabber-roster-sort-functions'.
+Return t if A is less than B."
+ (dolist (fn jabber-roster-sort-functions)
+ (let ((comparison (funcall fn a b)))
+ (cond
+ ((< comparison 0)
+ (return t))
+ ((> comparison 0)
+ (return nil))))))
+
+(defun jabber-roster-sort-by-status (a b)
+ "Sort roster items by online status.
+See `jabber-sort-order' for order used."
+ (flet ((order (item) (length (member (get item 'show) jabber-sort-order))))
+ (let ((a-order (order a))
+ (b-order (order b)))
+ ;; Note reversed test. Items with longer X-order go first.
+ (cond
+ ((< a-order b-order)
+ 1)
+ ((> a-order b-order)
+ -1)
+ (t
+ 0)))))
+
+(defun jabber-roster-sort-by-displayname (a b)
+ "Sort roster items by displayed name."
+ (cond
+ ((string-lessp a b) -1)
+ ((string= a b) 0)
+ (t 1)))
+
+(defun jabber-fix-status (status)
+ "Make status strings more readable"
+ (when (string-match "\n+$" status)
+ (setq status (replace-match "" t t status)))
+ (when jabber-remove-newlines
+ (while (string-match "\n" status)
+ (setq status (replace-match " " t t status))))
+ status)
+
+(defvar jabber-roster-positions nil
+ "Alist tracking positions of items in the roster.
+Keys are bare JID symbols. Values are conses of markers,
+marking the extent of the roster entry.")
+
+(defun jabber-display-roster ()
+ "switch to the main jabber buffer and refresh the roster display to reflect the current information"
+ (interactive)
+ (with-current-buffer (get-buffer-create jabber-roster-buffer)
+ (if (not (eq major-mode 'jabber-roster-mode))
+ (jabber-roster-mode))
+ (setq buffer-read-only nil)
+ ;; line-number-at-pos is in Emacs >= 21.4. Only used to avoid
+ ;; excessive scrolling when updating roster, so not absolutely
+ ;; necessary.
+ (let ((current-line (and (fboundp 'line-number-at-pos) (line-number-at-pos)))
+ (current-column (current-column)))
+ (erase-buffer)
+ (setq jabber-roster-positions nil)
+ (insert (jabber-propertize jabber-server 'face 'jabber-title-large) "\n")
+ (when jabber-roster-show-bindings
+ (insert "RET Open chat buffer C-k Delete roster item
+e Edit item s Send subscription request
+q Bury buffer i Get disco items
+I Get disco info b Browse
+j Join groupchat (MUC) v Get client version
+a Send presence
+C-c C-c Chat menu C-c C-m Multi-User Chat menu
+C-c C-i Info menu C-c C-r Roster menu
+C-c C-s Service menu
+"))
+ (insert "__________________________________\n\n")
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-2] #'jabber-send-presence)
+ (insert (jabber-propertize (concat (format " - %s"
+ (cdr (assoc *jabber-current-show* jabber-presence-strings)))
+ (if (not (zerop (length *jabber-current-status*)))
+ (format " (%s)"
+ (jabber-fix-status *jabber-current-status*)))
+ " -")
+ 'face (or (cdr (assoc *jabber-current-show* jabber-presence-faces))
+ 'jabber-roster-user-online)
+ ;;'mouse-face (cons 'background-color "light grey")
+ 'keymap map)
+ "\n__________________________________\n\n"))
+
+ (jabber-sort-roster)
+ (dolist (buddy *jabber-roster*)
+ (let ((entry-start (point)))
+ (jabber-display-roster-entry buddy)
+
+ ;; Keep track of this roster entry's position
+ (let ((entry (assq buddy jabber-roster-positions)))
+ (unless entry
+ (setq entry (cons buddy nil))
+ (push entry jabber-roster-positions))
+ (let ((marker-start (set-marker (make-marker) entry-start))
+ (marker-end (set-marker (make-marker) (point))))
+ ;; Text is inserted before start markers, but after
+ ;; end markers.
+ (set-marker-insertion-type marker-start t)
+ (setcdr entry (cons marker-start marker-end))))))
+ (insert "__________________________________")
+ (goto-char (point-min))
+ (setq buffer-read-only t)
+ (if (interactive-p)
+ (dolist (hook '(jabber-info-message-hooks jabber-alert-info-message-hooks))
+ (run-hook-with-args hook 'roster (current-buffer) (funcall jabber-alert-info-message-function 'roster (current-buffer)))))
+ (when current-line
+ (goto-line current-line)
+ (move-to-column current-column)))))
+
+(defun jabber-display-roster-entry (buddy)
+ "Format and insert a roster entry for BUDDY at point."
+ (let ((buddy-str (format-spec jabber-roster-line-format
+ (list
+ (cons ?c (if (get buddy 'connected) "*" " "))
+ (cons ?u (cdr (assoc (or (get buddy 'subscription) "none")
+ jabber-roster-subscription-display)))
+ (cons ?n (if (> (length (get buddy 'name)) 0)
+ (get buddy 'name)
+ (symbol-name buddy)))
+ (cons ?j (symbol-name buddy))
+ (cons ?r (or (get buddy 'resource) ""))
+ (cons ?s (or
+ (cdr (assoc (get buddy 'show) jabber-presence-strings))
+ (get buddy 'show)))
+ (cons ?S (if (get buddy 'status)
+ (jabber-fix-status (get buddy 'status))
+ ""))))))
+ (add-text-properties 0
+ (length buddy-str)
+ (list
+ 'face
+ (or (cdr (assoc (get buddy 'show) jabber-presence-faces))
+ 'jabber-roster-user-online)
+ ;;'mouse-face
+ ;;(cons 'background-color "light grey")
+ 'help-echo
+ (symbol-name buddy)
+ 'jabber-jid
+ (symbol-name buddy))
+ buddy-str)
+ ;; (let ((map (make-sparse-keymap))
+ ;; (chat-with-func (make-symbol (concat "jabber-chat-with" (symbol-name buddy)))))
+ ;; (fset chat-with-func `(lambda () (interactive) (jabber-chat-with ,(symbol-name buddy))))
+ ;; (define-key map [mouse-2] chat-with-func)
+ ;; (put-text-property 0
+ ;; (length buddy-str)
+ ;; 'keymap
+ ;; map
+ ;; buddy-str))
+ (insert buddy-str)
+
+ (when (or (eq jabber-show-resources 'always)
+ (and (eq jabber-show-resources 'sometimes)
+ (> (jabber-count-connected-resources buddy) 1)))
+ (dolist (resource (get buddy 'resources))
+ (when (plist-get (cdr resource) 'connected)
+ (let ((resource-str (format-spec jabber-resource-line-format
+ (list
+ (cons ?c "*")
+ (cons ?n (if (> (length (get buddy 'name)) 0)
+ (get buddy 'name)
+ (symbol-name buddy)))
+ (cons ?j (symbol-name buddy))
+ (cons ?r (if (> (length (car resource)) 0)
+ (car resource)
+ "empty"))
+ (cons ?s (or
+ (cdr (assoc (plist-get (cdr resource) 'show) jabber-presence-strings))
+ (plist-get (cdr resource) 'show)))
+ (cons ?S (if (plist-get (cdr resource) 'status)
+ (jabber-fix-status (plist-get (cdr resource) 'status))
+ ""))
+ (cons ?p (number-to-string (plist-get (cdr resource) 'priority)))))))
+ (add-text-properties 0
+ (length resource-str)
+ (list
+ 'face
+ (or (cdr (assoc (plist-get (cdr resource) 'show) jabber-presence-faces))
+ 'jabber-roster-user-online)
+ 'jabber-jid
+ (format "%s/%s" (symbol-name buddy) (car resource)))
+ resource-str)
+ (insert resource-str)))))))
+
+(defun jabber-presence-update-roster (who &rest ignore)
+ "Update roster without redrawing all of it, if possible."
+
+ (let* ((bare-jid (jabber-jid-symbol
+ (jabber-jid-user
+ (symbol-name who))))
+ (entry (assq bare-jid jabber-roster-positions))
+ (inhibit-read-only t))
+ (jabber-sort-roster)
+ (if (null entry)
+ (jabber-display-roster)
+ (let ((old-start (cadr entry))
+ (old-end (cddr entry))
+ (insert-before-this (cadr (memq bare-jid *jabber-roster*))))
+ (with-current-buffer jabber-roster-buffer
+ (delete-region old-start old-end)
+ (save-excursion
+ (let ((new-start
+ (marker-position
+ (if insert-before-this
+ ;; If this is not the last entry, go to start
+ ;; position of next entry.
+ (cadr (assq insert-before-this jabber-roster-positions))
+ ;; If this is the last entry, go to end position of second
+ ;; to last entry.
+ (cddr (car (last jabber-roster-positions 2)))))))
+ (goto-char new-start)
+ (jabber-display-roster-entry bare-jid)
+ (let ((marker-start (set-marker (make-marker) new-start))
+ (marker-end (set-marker (make-marker) (point))))
+ ;; Text is inserted before start markers, but after
+ ;; end markers.
+ (set-marker-insertion-type marker-start t)
+ (setcdr entry (cons marker-start marker-end))))))))))
+
+(defun jabber-go-to-next-jid ()
+ "Move the cursor to the next jid in the buffer"
+ (interactive)
+ (let ((next (next-single-property-change (point) 'jabber-jid)))
+ (when (and next
+ (not (get-text-property next 'jabber-jid)))
+ (setq next (next-single-property-change next 'jabber-jid)))
+ (unless next
+ (setq next (next-single-property-change (point-min) 'jabber-jid)))
+ (if next (goto-char (1+ next))
+ (goto-char (point-min)))))
+
+(provide 'jabber-roster)
+
+;;; arch-tag: 096af063-0526-4dd2-90fd-bc6b5ba07d32
diff --git a/jabber-sasl.el b/jabber-sasl.el
new file mode 100644
index 0000000..9dbfdb4
--- /dev/null
+++ b/jabber-sasl.el
@@ -0,0 +1,116 @@
+;; jabber-sasl.el - SASL authentication
+
+;; Copyright (C) 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'cl)
+
+;;; This file uses sasl.el from FLIM, and expects to find it. If it
+;;; can't be found, jabber-core.el catches the error.
+(require 'sasl)
+
+;;; Alternatives to FLIM would be the command line utility of GNU SASL,
+;;; or anything the Gnus people decide to use.
+
+;;; See XMPP-CORE and XMPP-IM for details about the protocol.
+
+(require 'jabber-xml)
+
+(defvar jabber-sasl-mechanism nil)
+(defvar jabber-sasl-client nil)
+(defvar jabber-sasl-step nil)
+
+(defun jabber-sasl-start-auth (stream-features)
+ ;; This shouldn't be necessary
+ ;;(setq jabber-call-on-connection nil)
+
+ ;; Reset our own state.
+ (setq jabber-sasl-mechanism nil)
+ (setq jabber-sasl-client nil)
+ (setq jabber-sasl-step nil)
+
+ ;; Hijack all stanzas for a while.
+ (setq jabber-short-circuit-input #'jabber-sasl-process-input)
+
+ ;; Find a suitable common mechanism.
+ (let ((mechanisms (car (jabber-xml-get-children stream-features 'mechanisms))))
+ (setq jabber-sasl-mechanism
+ (sasl-find-mechanism
+ (mapcar
+ (lambda (tag)
+ (car (jabber-xml-node-children tag)))
+ (jabber-xml-get-children mechanisms 'mechanism)))))
+ (if (null jabber-sasl-mechanism)
+ ;; Maybe we can use legacy authentication
+ (let ((node (find "http://jabber.org/features/iq-auth"
+ (jabber-xml-get-children stream-features 'auth)
+ :key #'(lambda (node) (jabber-xml-get-attribute node 'xmlns))
+ :test #'string=)))
+ (if node
+ (progn
+ (setq jabber-short-circuit-input nil)
+ (jabber-get-auth jabber-server))
+ (error "No suitable SASL mechanism found")))
+
+ ;; Start authentication.
+ (setq jabber-sasl-client (sasl-make-client jabber-sasl-mechanism jabber-username "xmpp" jabber-server))
+ (setq jabber-sasl-step (sasl-next-step jabber-sasl-client nil))
+ (jabber-send-sexp
+ `(auth ((xmlns . "urn:ietf:params:xml:ns:xmpp-sasl")
+ (mechanism . ,(sasl-mechanism-name jabber-sasl-mechanism)))
+ ,(when (sasl-step-data jabber-sasl-step)
+ (base64-encode-string (sasl-step-data jabber-sasl-step) t))))))
+
+(defun jabber-sasl-stop ()
+ (setq jabber-short-circuit-input nil))
+
+(defun jabber-sasl-process-input (xml-data)
+ (let ((sasl-read-passphrase #'jabber-read-passwd))
+ (cond
+ ((eq (car xml-data) 'challenge)
+ (sasl-step-set-data jabber-sasl-step (base64-decode-string (car (jabber-xml-node-children xml-data))))
+ (setq jabber-sasl-step (sasl-next-step jabber-sasl-client jabber-sasl-step))
+ (jabber-send-sexp
+ `(response ((xmlns . "urn:ietf:params:xml:ns:xmpp-sasl"))
+ ,(when (sasl-step-data jabber-sasl-step)
+ (base64-encode-string (sasl-step-data jabber-sasl-step) t)))))
+
+ ((eq (car xml-data) 'failure)
+ (ding)
+ (message "SASL authentication failure: %s"
+ (jabber-xml-node-name (car (jabber-xml-node-children xml-data))))
+ (sit-for 3)
+ (jabber-disconnect)
+ (jabber-sasl-stop))
+
+ ((eq (car xml-data) 'success)
+ (message "Authentication succeeded")
+ (setq *jabber-authenticated* t)
+ (jabber-sasl-stop)
+
+ ;; Now, we send another stream header.
+ (funcall jabber-conn-send-function
+ (concat
+ "<stream:stream to='"
+ jabber-server
+ "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"))
+ ;; now see what happens
+))))
+
+(provide 'jabber-sasl)
+;;; arch-tag: 2a4a234d-34d3-49dd-950d-518c899c0fd0
diff --git a/jabber-sawfish.el b/jabber-sawfish.el
new file mode 100644
index 0000000..378eb20
--- /dev/null
+++ b/jabber-sawfish.el
@@ -0,0 +1,40 @@
+;; jabber-sawfish.el - emacs-jabber interface to sawfish
+
+;; Copyright (C) 2005 - Mario Domenech Goulart
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(defcustom jabber-sawfish-display-time 3
+ "Time in seconds for displaying a jabber message through the
+Sawfish window manager."
+ :type 'integer
+ :group 'jabber-alerts)
+
+(defun jabber-sawfish-display-message (message)
+ "Displays MESSAGE through the Sawfish window manager."
+ (let ((process-connection-type nil))
+ (start-process-shell-command
+ "jabber-sawfish" nil "echo"
+ (concat "'(progn (display-message \""
+ message
+ "\")(make-timer (lambda () (display-message nil)) 3))' | sawfish-client - &> /dev/null"))))
+
+(define-jabber-alert sawfish "Display a message through the Sawfish window manager"
+ 'jabber-sawfish-display-message)
+
+(provide 'jabber-sawfish)
+;; arch-tag: 4F0154ED-5D05-11D9-9E6B-000A95C2FCD0
diff --git a/jabber-screen.el b/jabber-screen.el
new file mode 100644
index 0000000..7b00320
--- /dev/null
+++ b/jabber-screen.el
@@ -0,0 +1,29 @@
+;; jabber-screen.el - emacs-jabber interface to screen
+
+;; Copyright (C) 2005 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(defun jabber-screen-message (msg)
+ "Show MSG in screen"
+ (call-process "screen" nil nil nil "-X" "echo" msg))
+
+(define-jabber-alert screen "Show a message through the Screen terminal manager"
+ 'jabber-screen-message)
+
+(provide 'jabber-screen)
+;; arch-tag: B576ADDA-5D04-11D9-AA52-000A95C2FCD0
diff --git a/jabber-search.el b/jabber-search.el
new file mode 100644
index 0000000..d88a466
--- /dev/null
+++ b/jabber-search.el
@@ -0,0 +1,115 @@
+;; jabber-search.el - searching by JEP-0055, with x:data support
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-register)
+
+(add-to-list 'jabber-jid-service-menu
+ (cons "Search directory" 'jabber-get-search))
+(defun jabber-get-search (to)
+ "Send IQ get request in namespace \"jabber:iq:search\"."
+ (interactive (list (jabber-read-jid-completing "Search what database: ")))
+ (jabber-send-iq to
+ "get"
+ '(query ((xmlns . "jabber:iq:search")))
+ #'jabber-process-data #'jabber-process-register-or-search
+ #'jabber-report-success "Search field retrieval"))
+
+;; jabber-process-register-or-search logically comes here, rendering
+;; the search form, but since register and search are so similar,
+;; having two functions would be serious code duplication. See
+;; jabber-register.el.
+
+;; jabber-submit-search is called when the "submit" button of the
+;; search form is activated.
+(defun jabber-submit-search (&rest ignore)
+ "Submit search. See `jabber-process-register-or-search'."
+
+ (let ((text (concat "Search at " jabber-submit-to)))
+ (jabber-send-iq jabber-submit-to
+ "set"
+
+ (cond
+ ((eq jabber-form-type 'register)
+ `(query ((xmlns . "jabber:iq:search"))
+ ,@(jabber-parse-register-form)))
+ ((eq jabber-form-type 'xdata)
+ `(query ((xmlns . "jabber:iq:search"))
+ ,(jabber-parse-xdata-form)))
+ (t
+ (error "Unknown form type: %s" jabber-form-type)))
+ #'jabber-process-data #'jabber-process-search-result
+ #'jabber-report-success text))
+
+ (message "Search sent"))
+
+(defun jabber-process-search-result (xml-data)
+ "Receive and display search results."
+
+ ;; This function assumes that all search results come in one packet,
+ ;; which is not necessarily the case.
+ (let ((query (jabber-iq-query xml-data))
+ (have-xdata nil)
+ xdata fields (jid-fields 0))
+
+ ;; First, check for results in jabber:x:data form.
+ (dolist (x (jabber-xml-get-children query 'x))
+ (when (string= (jabber-xml-get-attribute x 'xmlns) "jabber:x:data")
+ (setq have-xdata t)
+ (setq xdata x)))
+
+ (if have-xdata
+ (jabber-render-xdata-search-results xdata)
+
+ (insert (jabber-propertize "Search results" 'face 'jabber-title-medium) "\n")
+
+ (setq fields '((first . (label "First name" column 0))
+ (last . (label "Last name" column 15))
+ (nick . (label "Nickname" column 30))
+ (jid . (label "JID" column 45))
+ (email . (label "E-mail" column 65))))
+ (setq jid-fields 1)
+
+ (dolist (field-cons fields)
+ (indent-to (plist-get (cdr field-cons) 'column) 1)
+ (insert (jabber-propertize (plist-get (cdr field-cons) 'label) 'face 'bold)))
+ (insert "\n\n")
+
+ ;; Now, the items
+ (dolist (item (jabber-xml-get-children query 'item))
+ (let ((start-of-line (point))
+ jid)
+
+ (dolist (field-cons fields)
+ (let ((field-plist (cdr field-cons))
+ (value (if (eq (car field-cons) 'jid)
+ (setq jid (jabber-xml-get-attribute item 'jid))
+ (car (jabber-xml-node-children (car (jabber-xml-get-children item (car field-cons))))))))
+ (indent-to (plist-get field-plist 'column) 1)
+ (if value (insert value))))
+
+ (if jid
+ (put-text-property start-of-line (point)
+ 'jabber-jid jid))
+ (insert "\n"))))))
+
+(provide 'jabber-search)
+
+;;; arch-tag: c39e9241-ab6f-4ac5-b1ba-7908bbae009c
diff --git a/jabber-si-client.el b/jabber-si-client.el
new file mode 100644
index 0000000..5925698
--- /dev/null
+++ b/jabber-si-client.el
@@ -0,0 +1,74 @@
+;; jabber-si-client.el - send stream requests, by JEP-0095
+
+;; Copyright (C) 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-iq)
+(require 'jabber-feature-neg)
+
+(defvar jabber-si-client-methods nil
+ "Supported SI stream methods for initiation.
+
+Each entry is a list, containing:
+ * The namespace URI of the stream method
+ * A function taking three arguments: JID, SID and profile function to call")
+
+(defun jabber-si-initiate (jid profile-namespace profile-data profile-function &optional mime-type)
+ "Try to initiate a stream to JID.
+PROFILE-NAMESPACE is, well, the namespace of the profile to use.
+PROFILE-DATA is the XML data to send within the SI request.
+PROFILE-FUNCTION is the function to call upon success.
+MIME-TYPE is the MIME type to specify.
+Returns the SID."
+
+ (let ((sid (apply 'format "emacs-sid-%d.%d.%d" (current-time))))
+ (jabber-send-iq jid "set"
+ `(si ((xmlns . "http://jabber.org/protocol/si")
+ (id . ,sid)
+ ,(if mime-type
+ (cons 'mime-type mime-type))
+ (profile . ,profile-namespace))
+ ,profile-data
+ (feature ((xmlns . "http://jabber.org/protocol/feature-neg"))
+ ,(jabber-fn-encode (list
+ (cons "stream-method"
+ (mapcar 'car jabber-si-client-methods)))
+ 'request)))
+ #'jabber-si-initiate-process (cons profile-function sid)
+ ;; XXX: use other function here?
+ #'jabber-report-success "Stream initiation")
+ sid))
+
+(defun jabber-si-initiate-process (xml-data closure-data)
+ "Act on response to our SI query."
+
+ (let* ((profile-function (car closure-data))
+ (sid (cdr closure-data))
+ (from (jabber-xml-get-attribute xml-data 'from))
+ (query (jabber-iq-query xml-data))
+ (feature-node (car (jabber-xml-get-children query 'feature)))
+ (feature-alist (jabber-fn-parse feature-node 'response))
+ (chosen-method (cadr (assoc "stream-method" feature-alist)))
+ (method-data (assoc chosen-method jabber-si-client-methods)))
+ ;; Our work is done. Hand it over to the stream method.
+ (let ((stream-negotiate (nth 1 method-data)))
+ (funcall stream-negotiate from sid profile-function))))
+
+(provide 'jabber-si-client)
+
+;;; arch-tag: e14ec451-3f18-4f36-b92a-e8a8aa1f5acd
diff --git a/jabber-si-server.el b/jabber-si-server.el
new file mode 100644
index 0000000..6c91b01
--- /dev/null
+++ b/jabber-si-server.el
@@ -0,0 +1,100 @@
+;; jabber-si-server.el - handle incoming stream requests, by JEP-0095
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-iq)
+(require 'jabber-disco)
+(require 'jabber-feature-neg)
+
+(add-to-list 'jabber-advertised-features "http://jabber.org/protocol/si")
+
+;; Now, stream methods push data to profiles. It could be the other
+;; way around; not sure which is better.
+(defvar jabber-si-profiles nil
+ "Supported SI profiles.
+
+Each entry is a list, containing:
+ * The namespace URI of the profile
+ * Accept function, taking entire IQ stanza, and signalling a 'forbidden'
+ error if request is declined; returning an XML node to return in
+ response, or nil of none needed
+ * Data function, taking JID of initiator, stream ID, and string
+ containing received data in binary form; receives `nil' on EOF.
+ Returns non-nil to keep connection; nil to close it.")
+
+(defvar jabber-si-stream-methods nil
+ "Supported SI stream methods.
+
+Each entry is a list, containing:
+ * The namespace URI of the stream method
+ * Accept function, taking JID of initiator, stream ID, profile
+ data function (as above), preparing to accept a request")
+
+(add-to-list 'jabber-iq-set-xmlns-alist
+ (cons "http://jabber.org/protocol/si" 'jabber-si-process))
+(defun jabber-si-process (xml-data)
+
+ (let* ((to (jabber-xml-get-attribute xml-data 'from))
+ (id (jabber-xml-get-attribute xml-data 'id))
+ (query (jabber-iq-query xml-data))
+ (profile (jabber-xml-get-attribute query 'profile))
+ (si-id (jabber-xml-get-attribute query 'id))
+ (feature (car (jabber-xml-get-children query 'feature))))
+ (message "Receiving SI with profile '%s'" profile)
+
+ (let (stream-method
+ ;; Find profile
+ (profile-data (assoc profile jabber-si-profiles)))
+ ;; Now, feature negotiation for stream type (errors
+ ;; don't match JEP-0095, so convert)
+ (condition-case err
+ (setq stream-method (jabber-fn-intersection
+ (jabber-fn-parse feature 'request)
+ (list (cons "stream-method" (mapcar 'car jabber-si-stream-methods)))))
+ (jabber-error
+ (jabber-signal-error "cancel" 'bad-request nil
+ '((no-valid-streams ((xmlns . "http://jabber.org/protocol/si")))))))
+ (unless profile-data
+ ;; profile not understood
+ (jabber-signal-error "cancel" 'bad-request nil
+ '((bad-profile ((xmlns . "http://jabber.org/protocol/si"))))))
+ (let* ((profile-accept-function (nth 1 profile-data))
+ ;; accept-function might throw a "forbidden" error
+ ;; on user cancel
+ (profile-response (funcall profile-accept-function xml-data))
+ (profile-data-function (nth 2 profile-data))
+ (stream-method-id (nth 1 (assoc "stream-method" stream-method)))
+ (stream-data (assoc stream-method-id jabber-si-stream-methods))
+ (stream-accept-function (nth 1 stream-data)))
+ ;; prepare stream for the transfer
+ (funcall stream-accept-function to si-id profile-data-function)
+ ;; return result of feature negotiation of stream type
+ (jabber-send-iq to "result"
+ `(si ((xmlns . "http://jabber.org/protocol/si"))
+ ,@profile-response
+ (feature ((xmlns . "http://jabber.org/protocol/feature-neg"))
+ ,(jabber-fn-encode stream-method 'response)))
+ nil nil nil nil
+ id)
+ ))))
+
+(provide 'jabber-si-server)
+
+;;; arch-tag: d3c75c66-4052-4cf5-8f04-8765adfc8b96
diff --git a/jabber-socks5.el b/jabber-socks5.el
new file mode 100644
index 0000000..2045351
--- /dev/null
+++ b/jabber-socks5.el
@@ -0,0 +1,310 @@
+;; jabber-socks5.el - SOCKS5 bytestreams by JEP-0065
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-iq)
+(require 'jabber-disco)
+(require 'jabber-si-server)
+(require 'jabber-si-client)
+
+(defvar jabber-socks5-pending-sessions nil
+ "List of pending sessions.
+
+Each entry is a list, containing:
+ * Stream ID
+ * Full JID of initiator
+ * Profile data function, to be called when data is received")
+
+(defvar jabber-socks5-active-sessions nil
+ "List of active sessions.
+
+Each entry is a list, containing:
+ * Network connection
+ * Stream ID
+ * Full JID of initiator
+ * Profile data function")
+
+(defcustom jabber-socks5-proxies nil
+ "JIDs of JEP-0065 proxies to use for file transfer.
+Put preferred ones first."
+ :type '(repeat string)
+ :group 'jabber
+; :set 'jabber-socks5-set-proxies)
+ )
+
+(defvar jabber-socks5-proxies-data nil
+ "Alist containing information about proxies.
+Keys of the alist are strings, the JIDs of the proxies.
+Values are \"streamhost\" XML nodes.")
+
+(add-to-list 'jabber-advertised-features "http://jabber.org/protocol/bytestreams")
+
+(add-to-list 'jabber-si-stream-methods
+ (list "http://jabber.org/protocol/bytestreams"
+ 'jabber-socks5-accept))
+
+(add-to-list 'jabber-si-client-methods
+ (list "http://jabber.org/protocol/bytestreams"
+ 'jabber-socks5-client-1))
+
+(defun jabber-socks5-set-proxies (symbol value)
+ "Set `jabber-socks5-proxies' and query proxies.
+This is the set function of `jabber-socks5-proxies-data'."
+ (set-default symbol value)
+ (when *jabber-connected*
+ (jabber-socks5-query-all-proxies)))
+
+(defun jabber-socks5-query-all-proxies ()
+ "Ask all proxies in `jabber-socks5-proxies' for connection information."
+ (interactive)
+ (setq jabber-socks5-proxies-data nil)
+ (dolist (proxy jabber-socks5-proxies)
+ (jabber-socks5-query-proxy proxy)))
+
+(defun jabber-socks5-query-proxy (jid)
+ "Query the SOCKS5 proxy specified by JID for IP and port number."
+ (jabber-send-iq jid "get"
+ '(query ((xmlns . "http://jabber.org/protocol/bytestreams")))
+ #'jabber-socks5-process-proxy-response t
+ #'jabber-socks5-process-proxy-response nil))
+
+(defun jabber-socks5-process-proxy-response (xml-data successp)
+ "Process response from proxy query."
+ (let* ((query (jabber-iq-query xml-data))
+ (from (jabber-xml-get-attribute xml-data 'from))
+ (streamhosts (jabber-xml-get-children query 'streamhost)))
+
+ (let ((existing-entry (assoc from jabber-socks5-proxies-data)))
+ (when existing-entry
+ (setq jabber-socks5-proxies-data
+ (delq existing-entry jabber-socks5-proxies-data))))
+
+ (when successp
+ (setq jabber-socks5-proxies-data
+ (cons (cons from streamhosts)
+ jabber-socks5-proxies-data)))
+ (message "%s from %s. %d of %d proxies have answered."
+ (if successp "Response" "Error") from
+ (length jabber-socks5-proxies-data) (length jabber-socks5-proxies))))
+
+(defun jabber-socks5-accept (jid sid profile-data-function)
+ "Remember that we are waiting for connection from JID, with stream id SID"
+ ;; asking the user for permission is done in the profile
+ (add-to-list 'jabber-socks5-pending-sessions
+ (list sid jid profile-data-function)))
+
+(add-to-list 'jabber-iq-set-xmlns-alist
+ (cons "http://jabber.org/protocol/bytestreams" 'jabber-socks5-process))
+(defun jabber-socks5-process (xml-data)
+ "Accept IQ get for SOCKS5 bytestream"
+ (let* ((jid (jabber-xml-get-attribute xml-data 'from))
+ (id (jabber-xml-get-attribute xml-data 'id))
+ (query (jabber-iq-query xml-data))
+ (sid (jabber-xml-get-attribute query 'sid))
+ (session (dolist (pending-session jabber-socks5-pending-sessions)
+ (when (and (equal sid (nth 0 pending-session))
+ (equal jid (nth 1 pending-session)))
+ (return pending-session))))
+ (profile-data-function (nth 2 session)))
+ ;; check that we really are expecting this session
+ (unless session
+ (jabber-signal-error "auth" 'not-acceptable))
+
+ (setq jabber-socks5-pending-sessions (delq session jabber-socks5-pending-sessions))
+ ;; find streamhost to connect to
+ (let* ((streamhosts (jabber-xml-get-children query 'streamhost))
+ (streamhost (dolist (streamhost streamhosts)
+ (let ((connection (jabber-socks5-connect streamhost sid jid (concat jabber-username "@" jabber-server "/" jabber-resource))))
+ (when connection
+ ;; We select the first streamhost that we are able to connect to.
+ (push (list connection sid jid profile-data-function)
+ jabber-socks5-active-sessions)
+ ;; Now set the filter, for the rest of the output
+ (set-process-filter connection #'jabber-socks5-filter)
+ (set-process-sentinel connection #'jabber-socks5-sentinel)
+ (return streamhost))))))
+ (unless streamhost
+ (jabber-signal-error "cancel" 'item-not-found))
+
+ ;; tell initiator which streamhost we use
+ (jabber-send-iq jid "result"
+ `(query ((xmlns . "http://jabber.org/protocol/bytestreams"))
+ (streamhost-used ((jid . ,(jabber-xml-get-attribute streamhost 'jid)))))
+ nil nil nil nil id)
+ ;; now, as data is sent, it will be passed to the profile.
+ )))
+
+(defun jabber-socks5-connect (streamhost sid initiator target)
+ "Attempt to connect to STREAMHOST, authenticating with SID, INITIATOR and TARGET.
+Return nil on error. Return connection object on success.
+
+STREAMHOST has the form
+\(streamhost ((host . HOST)
+ (port . PORT)))
+
+Zeroconf is not supported."
+ (message "Attempting SOCKS5 connection to %s (%s->%s, %s)" streamhost initiator target sid)
+ (condition-case e
+ (let ((coding-system-for-read 'binary)
+ (coding-system-for-write 'binary)
+ (host (jabber-xml-get-attribute streamhost 'host))
+ (port (string-to-number (jabber-xml-get-attribute streamhost 'port))))
+ ;; is this the best way to send binary network output?
+ (let ((socks5-connection (open-network-stream "socks5" (generate-new-buffer-name "socks5") host port)))
+ (with-current-buffer (process-buffer socks5-connection)
+ ;; version: 5. number of auth methods supported: 1.
+ ;; which one: no authentication.
+ (process-send-string socks5-connection (string 5 1 0))
+ ;; wait for response
+ (accept-process-output socks5-connection 15)
+ ;; should return:
+ ;; version: 5. auth method to use: none
+ (unless (string= (buffer-substring 1 3) (string 5 0))
+ (error "SOCKS5 authentication required"))
+
+ ;; send connect command
+ (let ((hash (sha1-string (concat sid initiator target))))
+ (process-send-string
+ socks5-connection
+ (concat (string 5 1 0 3 (length hash))
+ hash
+ (string 0 0))))
+
+ (accept-process-output socks5-connection 15)
+ (unless (string= (buffer-substring 3 5) (string 5 0))
+ (error "SOCKS5 failure"))
+
+ (message "SOCKS5 connection established")
+
+ ;; The information returned here is exactly the same that we sent...
+ ;; Not very exciting. Anyway, this part is done, we have a connection.
+ (let* ((address-type (aref (buffer-substring 6 7) 0))
+ (address-length (aref (buffer-substring 7 8) 0))
+ (address (buffer-substring 8 (+ 8 address-length)))
+ (address-port-string (buffer-substring (+ 8 address-length) (+ 8 address-length 2)))
+ (address-port (+
+ (* 256 (aref address-port-string 0))
+ (* 1 (aref address-port-string 1)))))
+ ;;(message "Address type: %d\nAddress: %s\nPort: %d" address-type address address-port)
+
+ ;; Delete all SOCKS5 data, leave room for the stream.
+ (delete-region 1 (+ 8 address-length 2)))
+
+ socks5-connection)))
+ (error
+ (message "SOCKS5 connection failed: %s" e)
+ nil)))
+
+(defun jabber-socks5-filter (connection data)
+ "Pass data from connection to profile data function"
+ (let* ((session (assq connection jabber-socks5-active-sessions))
+ (sid (nth 1 session))
+ (jid (nth 2 session))
+ (profile-data-function (nth 3 session)))
+ ;; If the data function requests it, tear down the connection.
+ (unless (funcall profile-data-function jid sid data)
+ (jabber-socks5-sentinel connection nil))))
+
+(defun jabber-socks5-sentinel (process event-string)
+ ;; Connection terminated. Shuffle together the remaining data,
+ ;; and kill the buffer.
+ (let* ((session (assq process jabber-socks5-active-sessions))
+ (buffer (process-buffer process))
+ (sid (nth 1 session))
+ (jid (nth 2 session))
+ (profile-data-function (nth 3 session)))
+ (kill-buffer buffer)
+ (delete-process process)
+ (funcall profile-data-function jid sid nil)
+ (setq jabber-socks5-active-sessions (delq session jabber-socks5-pending-sessions))))
+
+(defun jabber-socks5-client-1 (jid sid profile-function)
+ "Negotiate a SOCKS5 connection with JID.
+This function simply sends a request; the response is handled elsewhere."
+ ;; TODO: start our own server if we can.
+ (unless jabber-socks5-proxies
+ (error "No proxies defined. Set `jabber-socks5-proxies'."))
+ (unless jabber-socks5-proxies-data
+ (error "No proxy data available. Run `jabber-socks5-query-all-proxies'."))
+
+ ;; Sort the alist jabber-socks5-proxies-data such that the
+ ;; keys are in the same order as in jabber-socks5-proxies.
+ (setq jabber-socks5-proxies-data
+ (sort jabber-socks5-proxies-data
+ #'(lambda (a b)
+ (> (length (member (car a) jabber-socks5-proxies))
+ (length (member (car b) jabber-socks5-proxies))))))
+
+ (jabber-send-iq jid "set"
+ `(query ((xmlns . "http://jabber.org/protocol/bytestreams")
+ (sid . ,sid))
+ ,@(mapcar
+ #'(lambda (proxy)
+ (mapcar
+ #'(lambda (streamhost)
+ (list 'streamhost
+ (list (cons 'jid (jabber-xml-get-attribute streamhost 'jid))
+ (cons 'host (jabber-xml-get-attribute streamhost 'host))
+ (cons 'port (jabber-xml-get-attribute streamhost 'port)))))
+ (cdr proxy)))
+ jabber-socks5-proxies-data))
+ `(lambda (xml-data closure-data)
+ (jabber-socks5-client-2 xml-data ,jid ,sid ,profile-function)) nil
+ ;; TODO: error handling
+ #'jabber-report-success "SOCKS5 negotiation"))
+
+(defun jabber-socks5-client-2 (xml-data jid sid profile-function)
+ "Contact has selected a streamhost to use. Connect to the proxy."
+ (let* ((query (jabber-iq-query xml-data))
+ (streamhost-used (car (jabber-xml-get-children query 'streamhost-used)))
+ (proxy-used (jabber-xml-get-attribute streamhost-used 'jid))
+ connection)
+ (let ((streamhosts-left (cdr (assoc proxy-used jabber-socks5-proxies-data))))
+ (while (and streamhosts-left (not connection))
+ (setq connection
+ (jabber-socks5-connect (car streamhosts-left)
+ sid
+ (concat jabber-username "@" jabber-server "/" jabber-resource)
+ jid))
+ (setq streamhosts-left (cdr streamhosts-left))))
+ (unless connection
+ (error "Couldn't connect to proxy %s" proxy-used))
+
+ ;; Activation is only needed for proxies.
+ (jabber-send-iq proxy-used "set"
+ `(query ((xmlns . "http://jabber.org/protocol/bytestreams")
+ (sid . ,sid))
+ (activate () ,jid))
+ `(lambda (xml-data closure-data)
+ (jabber-socks5-client-3 xml-data ,jid ,sid ,profile-function ,connection)) nil
+ ;; TODO: report error to contact?
+ #'jabber-report-success "Proxy activation")))
+
+(defun jabber-socks5-client-3 (xml-data jid sid profile-function proxy-connection)
+ "Proxy is activated. Start the transfer."
+ ;; The response from the proxy does not contain any interesting
+ ;; information, beyond success confirmation.
+
+ (funcall profile-function jid sid `(lambda (data)
+ (process-send-string ,proxy-connection data))))
+
+(provide 'jabber-socks5)
+
+;;; arch-tag: 9e70dfea-2522-40c6-a79f-302c8fb82ac5
diff --git a/jabber-util.el b/jabber-util.el
new file mode 100644
index 0000000..112d799
--- /dev/null
+++ b/jabber-util.el
@@ -0,0 +1,444 @@
+;; jabber-util.el - various utility functions
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(defvar jabber-jid-history nil
+ "History of entered JIDs")
+
+(defvar *jabber-sound-playing* nil
+ "is a sound playing right now?")
+
+(cond
+ ((fboundp 'replace-in-string)
+ (defsubst jabber-replace-in-string (str regexp newtext)
+ (replace-in-string str regexp newtext t)))
+ ((fboundp 'replace-regexp-in-string)
+ (defsubst jabber-replace-in-string (str regexp newtext)
+ (replace-regexp-in-string regexp newtext str t t))))
+
+;;; XEmacs compatibility. Stolen from ibuffer.el
+(if (fboundp 'propertize)
+ (defalias 'jabber-propertize 'propertize)
+ (defun jabber-propertize (string &rest properties)
+ "Return a copy of STRING with text properties added.
+
+ [Note: this docstring has been copied from the Emacs 21 version]
+
+First argument is the string to copy.
+Remaining arguments form a sequence of PROPERTY VALUE pairs for text
+properties to add to the result."
+ (let ((str (copy-sequence string)))
+ (add-text-properties 0 (length str)
+ properties
+ str)
+ str)))
+
+(unless (fboundp 'bound-and-true-p)
+ (defmacro bound-and-true-p (var)
+ "Return the value of symbol VAR if it is bound, else nil."
+ `(and (boundp (quote ,var)) ,var)))
+
+;;; more XEmacs compatibility
+;;; Preserve input method when entering a minibuffer
+(if (featurep 'xemacs)
+ ;; I don't know how to do this
+ (defsubst jabber-read-with-input-method (prompt &optional initial-contents history default-value)
+ (read-string prompt initial-contents history default-value))
+ (defsubst jabber-read-with-input-method (prompt &optional initial-contents history default-value)
+ (read-string prompt initial-contents history default-value t)))
+
+(unless (fboundp 'delete-and-extract-region)
+ (defsubst delete-and-extract-region (start end)
+ (prog1
+ (buffer-substring start end)
+ (delete-region start end))))
+
+(unless (fboundp 'access-file)
+ (defsubst access-file (filename error-message)
+ (unless (file-readable-p filename)
+ (error error-message))))
+
+(if (fboundp 'float-time)
+ (defalias 'jabber-float-time 'float-time)
+ (defun jabber-float-time (&optional specified-time)
+ (unless specified-time
+ (setq specified-time (current-time)))
+ ;; second precision is good enough for us
+ (+ (* 65536.0 (car specified-time))
+ (cadr specified-time))))
+
+(cond
+ ((fboundp 'cancel-timer)
+ (defalias 'jabber-cancel-timer 'cancel-timer))
+ ((fboundp 'delete-itimer)
+ (defalias 'jabber-cancel-timer 'delete-itimer))
+ (t
+ (error "No `cancel-timer' function found")))
+
+(defun jabber-jid-username (string)
+ "return the username portion of a JID, or nil if no username"
+ (when (string-match "\\(.*\\)@.*\\(/.*\\)?" string)
+ (match-string 1 string)))
+
+(defun jabber-jid-user (string)
+ "return the user (username@server) portion of a JID"
+ ;;transports don't have @, so don't require it
+ ;;(string-match ".*@[^/]*" string)
+ (string-match "[^/]*" string)
+ (match-string 0 string))
+
+(defun jabber-jid-server (string)
+ "Return the server portion of a JID."
+ (string-match "^\\(.*@\\)?\\([^@/]+\\)\\(/.*\\)?$" string)
+ (match-string 2 string))
+
+(defun jabber-jid-rostername (string)
+ "return the name of the user, if given in roster, else nil"
+ (let ((user (jabber-jid-symbol string)))
+ (if (> (length (get user 'name)) 0)
+ (get user 'name))))
+
+(defun jabber-jid-displayname (string)
+ "return the name of the user, if given in roster, else username@server"
+ (or (jabber-jid-rostername string)
+ (jabber-jid-user (if (symbolp string)
+ (symbol-name string)
+ string))))
+
+(defun jabber-jid-resource (string)
+ "return the resource portion of a JID, or nil if there is none."
+ (when (string-match "^\\(\\([^/]*@\\)?[^/]*\\)/\\(.*\\)" string)
+ (match-string 3 string)))
+
+(defun jabber-jid-symbol (string)
+ "return the symbol for the given JID"
+ ;; If it's already a symbol, just return it.
+ (if (symbolp string)
+ string
+ ;; XXX: "downcase" is poor man's nodeprep. See XMPP CORE.
+ (intern (downcase (jabber-jid-user string)) jabber-jid-obarray)))
+
+(defun jabber-my-jid-p (jid)
+ "Return non-nil if the specified JID is equal to the user's JID, modulo resource."
+ (equal (jabber-jid-user jid)
+ (concat jabber-username "@" jabber-server)))
+
+(defun jabber-read-jid-completing (prompt &optional subset require-match default)
+ "read a jid out of the current roster from the minibuffer.
+If SUBSET is non-nil, it should be a list of symbols from which
+the JID is to be selected, instead of using the entire roster.
+If REQUIRE-MATCH is non-nil, the JID must be in the list used.
+If DEFAULT is non-nil, it's used as the default value, otherwise
+the default is inferred from context."
+ (let ((jid-at-point (or
+ (and default
+ ;; default can be either a symbol or a string
+ (if (symbolp default)
+ (symbol-name default)
+ default))
+ (get-text-property (point) 'jabber-jid)
+ (bound-and-true-p jabber-chatting-with)
+ (bound-and-true-p jabber-group)))
+ (completion-ignore-case t)
+ (jid-completion-table (mapcar #'(lambda (item)
+ (cons (symbol-name item) item))
+ (or subset *jabber-roster*))))
+ (dolist (item (or subset *jabber-roster*))
+ (if (get item 'name)
+ (push (cons (get item 'name) item) jid-completion-table)))
+ ;; if the default is not in the allowed subset, it's not a good default
+ (if (and subset (not (assoc jid-at-point jid-completion-table)))
+ (setq jid-at-point nil))
+ (let ((input
+ (completing-read (concat prompt
+ (if jid-at-point
+ (format "(default %s) " jid-at-point)))
+ jid-completion-table
+ nil require-match nil 'jabber-jid-history jid-at-point)))
+ (if (and input (assoc-ignore-case input jid-completion-table))
+ (symbol-name (cdr (assoc-ignore-case input jid-completion-table)))
+ (and (not (zerop (length input)))
+ input)))))
+
+(defun jabber-read-node (prompt)
+ "Read node name, taking default from disco item at point."
+ (let ((node-at-point (get-text-property (point) 'jabber-node)))
+ (read-string (concat prompt
+ (if node-at-point
+ (format "(default %s) " node-at-point)))
+ node-at-point)))
+
+(defun jabber-read-passwd (&optional prompt)
+ "Read Jabber password, either from customized variable or from minibuffer.
+See `jabber-password'."
+ (or jabber-password (read-passwd (or prompt "Jabber password: "))))
+
+(defun jabber-iq-query (xml-data)
+ "Return the query part of an IQ stanza.
+An IQ stanza may have zero or one query child, and zero or one <error/> child.
+The query child is often but not always <query/>."
+ (let (query)
+ (dolist (x (jabber-xml-node-children xml-data))
+ (if (and
+ (listp x)
+ (not (eq (jabber-xml-node-name x) 'error)))
+ (setq query x)))
+ query))
+
+(defun jabber-iq-error (xml-data)
+ "Return the <error/> part of an IQ stanza, if any."
+ (car (jabber-xml-get-children xml-data 'error)))
+
+(defun jabber-iq-xmlns (xml-data)
+ "Return the namespace of an IQ stanza, i.e. the namespace of its query part."
+ (jabber-xml-get-attribute (jabber-iq-query xml-data) 'xmlns))
+
+(defun jabber-x-delay (xml-data)
+ "Return timestamp given a <x/> tag in namespace jabber:x:delay.
+Return nil if no such data available."
+ (when (and (eq (jabber-xml-node-name xml-data) 'x)
+ (string= (jabber-xml-get-attribute xml-data 'xmlns) "jabber:x:delay"))
+ (let ((stamp (jabber-xml-get-attribute xml-data 'stamp)))
+ (if (and (stringp stamp)
+ (= (length stamp) 17))
+ (jabber-parse-legacy-time stamp)))))
+
+(defun jabber-parse-legacy-time (timestamp)
+ "Parse timestamp in ccyymmddThh:mm:ss format (UTC) and return as internal time value."
+ (let ((year (string-to-number (substring timestamp 0 4)))
+ (month (string-to-number (substring timestamp 4 6)))
+ (day (string-to-number (substring timestamp 6 8)))
+ (hour (string-to-number (substring timestamp 9 11)))
+ (minute (string-to-number (substring timestamp 12 14)))
+ (second (string-to-number (substring timestamp 15 17))))
+ (encode-time second minute hour day month year 0)))
+
+(defun jabber-encode-legacy-time (timestamp)
+ "Parse TIMESTAMP as internal time value and encode as ccyymmddThh:mm:ss (UTC)."
+ (if (featurep 'xemacs)
+ ;; XEmacs doesn't have `universal' argument to format-time-string,
+ ;; so we have to do it ourselves.
+ (format-time-string "%Y%m%dT%H:%M:%S"
+ (time-subtract timestamp
+ (list 0 (car (current-time-zone)))))
+ (format-time-string "%Y%m%dT%H:%M:%S" timestamp t)))
+
+(defun jabber-encode-time (time)
+ "Convert TIME to a string by JEP-0082.
+TIME is in a format accepted by `format-time-string'."
+ (let ((time-zone-offset (nth 0 (current-time-zone))))
+ (if (null time-zone-offset)
+ ;; no time zone information available; pretend it's UTC
+ (format-time-string "%Y-%m-%dT%H:%M:%SZ" time)
+ (let* ((positivep (>= time-zone-offset 0))
+ (hours (/ (abs time-zone-offset) 3600))
+ (minutes (/ (% (abs time-zone-offset) 3600) 60)))
+ (format "%s%s%02d:%02d" (format-time-string "%Y-%m-%dT%H:%M:%S" time)
+ (if positivep "+" "-") hours minutes)))))
+
+(defun jabber-parse-time (time)
+ "Parse the DateTime encoded in TIME according to JEP-0082."
+ (let* ((year (string-to-number (substring time 0 4)))
+ (month (string-to-number (substring time 5 7)))
+ (day (string-to-number (substring time 8 10)))
+ (hour (string-to-number (substring time 11 13)))
+ (minute (string-to-number (substring time 14 16)))
+ (second (string-to-number (substring time 17 19)))
+ ;; fractions are optional
+ (fraction (if (eq (aref time 19) ?.)
+ (string-to-number (substring time 20 23))))
+ (timezone (substring time (if fraction 23 19))))
+ ;; timezone is either Z (UTC) or [+-]HH:MM
+ (let ((timezone-seconds
+ (if (string= timezone "Z")
+ 0
+ (* (if (eq (aref timezone 0) ?+) 1 -1)
+ (* 60 (+ (* 60 (string-to-number (substring timezone 1 3)))
+ (string-to-number (substring timezone 4 6))))))))
+ (encode-time second minute hour day month year timezone-seconds))))
+
+(defun jabber-report-success (xml-data context)
+ "IQ callback reporting success or failure of the operation.
+CONTEXT is a string describing the action."
+ (let ((type (jabber-xml-get-attribute xml-data 'type)))
+ (message (concat context
+ (if (string= type "result")
+ " succeeded"
+ (concat
+ " failed: "
+ (let ((the-error (jabber-iq-error xml-data)))
+ (if the-error
+ (jabber-parse-error the-error)
+ "No error message given"))))))))
+
+(defconst jabber-error-messages
+ (list
+ (cons 'bad-request "Bad request")
+ (cons 'conflict "Conflict")
+ (cons 'feature-not-implemented "Feature not implemented")
+ (cons 'forbidden "Forbidden")
+ (cons 'gone "Gone")
+ (cons 'internal-server-error "Internal server error")
+ (cons 'item-not-found "Item not found")
+ (cons 'jid-malformed "JID malformed")
+ (cons 'not-acceptable "Not acceptable")
+ (cons 'not-allowed "Not allowed")
+ (cons 'not-authorized "Not authorized")
+ (cons 'payment-required "Payment required")
+ (cons 'recipient-unavailable "Recipient unavailable")
+ (cons 'redirect "Redirect")
+ (cons 'registration-required "Registration required")
+ (cons 'remote-server-not-found "Remote server not found")
+ (cons 'remote-server-timeout "Remote server timeout")
+ (cons 'resource-constraint "Resource constraint")
+ (cons 'service-unavailable "Service unavailable")
+ (cons 'subscription-required "Subscription required")
+ (cons 'undefined-condition "Undefined condition")
+ (cons 'unexpected-request "Unexpected request"))
+ "String descriptions of XMPP stanza errors")
+
+(defconst jabber-legacy-error-messages
+ (list
+ (cons 302 "Redirect")
+ (cons 400 "Bad request")
+ (cons 401 "Unauthorized")
+ (cons 402 "Payment required")
+ (cons 403 "Forbidden")
+ (cons 404 "Not found")
+ (cons 405 "Not allowed")
+ (cons 406 "Not acceptable")
+ (cons 407 "Registration required")
+ (cons 408 "Request timeout")
+ (cons 409 "Conflict")
+ (cons 500 "Internal server error")
+ (cons 501 "Not implemented")
+ (cons 502 "Remote server error")
+ (cons 503 "Service unavailable")
+ (cons 504 "Remote server timeout")
+ (cons 510 "Disconnected"))
+ "String descriptions of legacy errors (JEP-0086)")
+
+(defun jabber-parse-error (error-xml)
+ "Parse the given <error/> tag and return a string fit for human consumption.
+See secton 9.3, Stanza Errors, of XMPP Core, and JEP-0086, Legacy Errors."
+ (let ((error-type (jabber-xml-get-attribute error-xml 'type))
+ (error-code (jabber-xml-get-attribute error-xml 'code))
+ condition text)
+ (if error-type
+ ;; If the <error/> tag has a type element, it is new-school.
+ (dolist (child (jabber-xml-node-children error-xml))
+ (when (string=
+ (jabber-xml-get-attribute child 'xmlns)
+ "urn:ietf:params:xml:ns:xmpp-stanzas")
+ (if (eq (jabber-xml-node-name child) 'text)
+ (setq text (car (jabber-xml-node-children child)))
+ (setq condition
+ (or (cdr (assq (jabber-xml-node-name child) jabber-error-messages))
+ (symbol-name (jabber-xml-node-name child)))))))
+ (setq condition (or (cdr (assq (string-to-number error-code) jabber-legacy-error-messages))
+ error-code))
+ (setq text (car (jabber-xml-node-children error-xml))))
+ (concat condition
+ (if text (format ": %s" text)))))
+
+(defvar jabber-stream-error-messages
+ (list
+ (cons 'bad-format "Bad XML format")
+ (cons 'bad-namespace-prefix "Bad namespace prefix")
+ (cons 'conflict "Conflict")
+ (cons 'connection-timeout "Connection timeout")
+ (cons 'host-gone "Host gone")
+ (cons 'host-unknown "Host unknown")
+ (cons 'improper-addressing "Improper addressing") ; actually only s2s
+ (cons 'internal-server-error "Internal server error")
+ (cons 'invalid-from "Invalid from")
+ (cons 'invalid-id "Invalid id")
+ (cons 'invalid-namespace "Invalid namespace")
+ (cons 'invalid-xml "Invalid XML")
+ (cons 'not-authorized "Not authorized")
+ (cons 'policy-violation "Policy violation")
+ (cons 'remote-connection-failed "Remote connection failed")
+ (cons 'resource-constraint "Resource constraint")
+ (cons 'restricted-xml "Restricted XML")
+ (cons 'see-other-host "See other host")
+ (cons 'system-shutdown "System shutdown")
+ (cons 'undefined-condition "Undefined condition")
+ (cons 'unsupported-encoding "Unsupported encoding")
+ (cons 'unsupported-stanza-type "Unsupported stanza type")
+ (cons 'unsupported-version "Unsupported version")
+ (cons 'xml-not-well-formed "XML not well formed"))
+ "String descriptions of XMPP stream errors")
+
+(defun jabber-parse-stream-error (error-xml)
+ "Parse the given <stream:error/> tag and return a sting fit for human consumption."
+ (let ((text-node (car (jabber-xml-get-children error-xml 'text)))
+ condition)
+ ;; as we don't know the node name of the condition, we have to
+ ;; search for it.
+ (dolist (node (jabber-xml-node-children error-xml))
+ (when (and (string= (jabber-xml-get-attribute node 'xmlns)
+ "urn:ietf:params:xml:ns:xmpp-streams")
+ (assq (jabber-xml-node-name node)
+ jabber-stream-error-messages))
+ (setq condition (jabber-xml-node-name node))
+ (return)))
+ (concat (if condition (cdr (assq condition jabber-stream-error-messages))
+ "Unknown stream error")
+ (if (and text-node (stringp (car (jabber-xml-node-children text-node))))
+ (concat ": " (car (jabber-xml-node-children text-node)))))))
+
+(put 'jabber-error
+ 'error-conditions
+ '(error jabber-error))
+(put 'jabber-error
+ 'error-message
+ "Jabber error")
+
+(defun jabber-signal-error (error-type condition &optional text app-specific)
+ "Signal an error to be sent by Jabber.
+ERROR-TYPE is one of \"cancel\", \"continue\", \"modify\", \"auth\"
+and \"wait\".
+CONDITION is a symbol denoting a defined XMPP condition.
+TEXT is a string to be sent in the error message, or nil for no text.
+APP-SPECIFIC is a list of extra XML tags.
+
+See section 9.3 of XMPP Core."
+ (signal 'jabber-error
+ (list error-type condition text app-specific)))
+
+(defun jabber-play-sound-file (soundfile)
+ (if (not *jabber-sound-playing*)
+ (progn
+ (setq *jabber-sound-playing* t)
+ (run-with-idle-timer 0.01 nil
+ #'(lambda (sf)
+ (condition-case nil
+ ;; play-sound-file might display "Could not set sample rate" in
+ ;; echo area. Don't let this erase the previous message.
+ (let ((old-message (current-message)))
+ (play-sound-file sf)
+ (setq *jabber-sound-playing* nil)
+ (message "%s" old-message))
+ (error (setq *jabber-sound-playing* nil))))
+ soundfile))))
+
+(provide 'jabber-util)
+
+;;; arch-tag: cfbb73ac-e2d7-4652-a08d-dc789bcded8a
diff --git a/jabber-vcard.el b/jabber-vcard.el
new file mode 100644
index 0000000..42cd1d7
--- /dev/null
+++ b/jabber-vcard.el
@@ -0,0 +1,468 @@
+;;; jabber-vcard.el --- vcards according to JEP-0054
+
+;; Copyright (C) 2005 Magnus Henoch
+
+;; Author: Magnus Henoch <mange@freemail.hu>
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; There are great variations in Jabber vcard implementations. This
+;; one adds some spice to the mix, while trying to follow the JEP
+;; closely.
+
+;; Fields not implemented: GEO, LOGO, AGENT, ORG, CATEGORIES, SOUND,
+;; CLASS, KEY, PHOTO.
+
+;; The internal data structure used for vCards is an alist. All
+;; keys are uppercase symbols.
+;;
+;; FN, NICKNAME, BDAY, JABBERID, MAILER, TZ, TITLE, ROLE, NOTE,
+;; PRODID, REV, SORT-STRING, UID, URL, DESC:
+;; Value is a string.
+;;
+;; N:
+;; Value is an alist, with keys FAMILY, GIVEN, MIDDLE, PREFIX and SUFFIX.
+;;
+;; ADR:
+;; Value is a list, each element representing a separate address.
+;; The car of each address is a list of types; possible values are
+;; HOME, WORK, POSTAL, PARCEL, DOM, INTL, PREF.
+;; The cdr of each address is an alist, with keys POBOX, EXTADD,
+;; STREET, LOCALITY, REGION, PCODE, CTRY, and values being strings.
+;;
+;; TEL:
+;; Value is a list, each element representing a separate phone number.
+;; The car of each number is a list of types; possible values are
+;; HOME, WORK, VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN,
+;; PCS, PREF
+;; The cdr is the phone number as a string.
+;;
+;; EMAIL:
+;; Value is a list, each element representing a separate e-mail address.
+;; The car of each address is a list of types; possible values are
+;; HOME, WORK, INTERNET, PREF, X400. At least one of INTERNET and
+;; X400 is always present.
+;; The cdr is the address as a string.
+
+;;; Code:
+
+(require 'jabber-core)
+(require 'jabber-widget)
+(require 'jabber-iq)
+
+(defun jabber-vcard-parse (vcard)
+ "Parse the vCard XML structure given in VCARD.
+The top node should be the `vCard' node."
+ ;; Hm... stpeter has a <query/> as top node...
+ ;;(unless (eq (jabber-xml-node-name vcard) 'vCard)
+ ;; (error "Invalid vCard"))
+ (let (result)
+ (dolist (verbatim-node '(FN NICKNAME BDAY JABBERID MAILER TZ
+ TITLE ROLE NOTE PRODID REV SORT-STRING
+ UID URL DESC))
+ ;; There should only be one of each of these. They are
+ ;; used verbatim.
+ (let ((node (car (jabber-xml-get-children vcard
+ verbatim-node))))
+ ;; Some clients include the node, but without data
+ (when (car (jabber-xml-node-children node))
+ (push (cons (jabber-xml-node-name node)
+ (car (jabber-xml-node-children node)))
+ result))))
+
+ ;; Name components
+ (let ((node (car (jabber-xml-get-children vcard 'N))))
+ ;; Subnodes are FAMILY, GIVEN, MIDDLE, PREFIX, SUFFIX
+ (push (cons 'N
+ (let (name)
+ (dolist (subnode (jabber-xml-node-children node))
+ (when (and (memq (jabber-xml-node-name subnode)
+ '(FAMILY GIVEN MIDDLE PREFIX SUFFIX))
+ (not (zerop (length
+ (car (jabber-xml-node-children
+ subnode))))))
+ (push (cons (jabber-xml-node-name subnode)
+ (car (jabber-xml-node-children
+ subnode)))
+ name)))
+ name))
+ result))
+
+ ;; There can be several addresses
+ (let (addresses)
+ (dolist (adr (jabber-xml-get-children vcard 'ADR))
+ ;; Find address type(s)
+ (let (types)
+ (dolist (possible-type '(HOME WORK POSTAL PARCEL DOM INTL PREF))
+ (when (jabber-xml-get-children adr possible-type)
+ (push possible-type types)))
+
+ (let (components)
+ (dolist (component (jabber-xml-node-children adr))
+ (when (and (memq (jabber-xml-node-name component)
+ '(POBOX EXTADD STREET LOCALITY REGION
+ PCODE CTRY))
+ (not (zerop (length
+ (car (jabber-xml-node-children
+ component))))))
+ (push (cons (jabber-xml-node-name component)
+ (car (jabber-xml-node-children component)))
+ components)))
+
+ (push (cons types components) addresses))))
+
+ (when addresses
+ (push (cons 'ADR addresses) result)))
+
+ ;; Likewise for phone numbers
+ (let (phone-numbers)
+ (dolist (tel (jabber-xml-get-children vcard 'TEL))
+ ;; Find phone type(s)
+ (let ((number (car (jabber-xml-node-children
+ (car (jabber-xml-get-children tel 'NUMBER)))))
+ types)
+ ;; Some clients put no NUMBER node. Avoid that.
+ (when number
+ (dolist (possible-type '(HOME WORK VOICE FAX PAGER MSG CELL
+ VIDEO BBS MODEM ISDN PCS PREF))
+ (when (jabber-xml-get-children tel possible-type)
+ (push possible-type types)))
+
+ (push (cons types number) phone-numbers))))
+
+ (when phone-numbers
+ (push (cons 'TEL phone-numbers) result)))
+
+ ;; And for e-mail addresses
+ (let (e-mails)
+ (dolist (email (jabber-xml-get-children vcard 'EMAIL))
+ (let ((userid (car (jabber-xml-node-children
+ (car (jabber-xml-get-children email 'USERID)))))
+ types)
+ ;; Some clients put no USERID node. Avoid that.
+ (when userid
+ (dolist (possible-type '(HOME WORK INTERNET PREF X400))
+ (when (jabber-xml-get-children email possible-type)
+ (push possible-type types)))
+ (unless (or (memq 'INTERNET types)
+ (memq 'X400 types))
+ (push 'INTERNET types))
+
+ (push (cons types userid) e-mails))))
+
+ (when e-mails
+ (push (cons 'EMAIL e-mails) result)))
+
+ result))
+
+(defun jabber-vcard-reassemble (parsed)
+ "Create a vCard XML structure from PARSED."
+ `(vCard ((xmlns . "vcard-temp"))
+ ;; Put in simple fields
+ ,@(mapcar
+ (lambda (field)
+ (when (and (assq (car field) jabber-vcard-fields)
+ (not (zerop (length (cdr field)))))
+ (list (car field) nil (cdr field))))
+ parsed)
+ ;; Put in decomposited name
+ (N nil
+ ,@(mapcar
+ (lambda (name-part)
+ (when (not (zerop (length (cdr name-part))))
+ (list (car name-part) nil (cdr name-part))))
+ (cdr (assq 'N parsed))))
+ ;; Put in addresses
+ ,@(mapcar
+ (lambda (address)
+ (append '(ADR) '(())
+ (mapcar 'list (nth 0 address))
+ (mapcar (lambda (field)
+ (list (car field) nil (cdr field)))
+ (cdr address))))
+ (cdr (assq 'ADR parsed)))
+ ;; Put in phone numbers
+ ,@(mapcar
+ (lambda (phone)
+ (append '(TEL) '(())
+ (mapcar 'list (car phone))
+ (list (list 'NUMBER nil (cdr phone)))))
+ (cdr (assq 'TEL parsed)))
+ ;; Put in e-mail addresses
+ ,@(mapcar
+ (lambda (email)
+ (append '(EMAIL) '(())
+ (mapcar 'list (car email))
+ (list (list 'USERID nil (cdr email)))))
+ (cdr (assq 'EMAIL parsed)))))
+
+(add-to-list 'jabber-jid-info-menu
+ (cons "Request vcard" 'jabber-vcard-get))
+
+(defun jabber-vcard-get (jid)
+ "Request vcard from JID."
+ (interactive (list (jabber-read-jid-completing "Request vcard from: ")))
+ (jabber-send-iq jid
+ "get"
+ '(vCard ((xmlns . "vcard-temp")))
+ #'jabber-process-data #'jabber-vcard-display
+ #'jabber-process-data "Vcard request failed"))
+
+(defun jabber-vcard-edit ()
+ "Edit your own vcard."
+ (interactive)
+ (jabber-send-iq nil
+ "get"
+ '(vCard ((xmlns . "vcard-temp")))
+ #'jabber-vcard-do-edit nil
+ #'jabber-report-success "Vcard request failed"))
+
+(defconst jabber-vcard-fields '((FN . "Full name")
+ (NICKNAME . "Nickname")
+ (BDAY . "Birthday")
+ (URL . "URL")
+ (JABBERID . "JID")
+ (MAILER . "User agent")
+ (TZ . "Time zone")
+ (TITLE . "Title")
+ (ROLE . "Role")
+ (REV . "Last changed")
+ (DESC . "Description")
+ (NOTE . "Note")))
+
+(defconst jabber-vcard-name-fields '((PREFIX . "Prefix")
+ (GIVEN . "Given name")
+ (MIDDLE . "Middle name")
+ (FAMILY . "Family name")
+ (SUFFIX . "Suffix")))
+
+(defconst jabber-vcard-phone-types '((HOME . "Home")
+ (WORK . "Work")
+ (VOICE . "Voice")
+ (FAX . "Fax")
+ (PAGER . "Pager")
+ (MSG . "Message")
+ (CELL . "Cell phone")
+ (VIDEO . "Video")
+ (BBS . "BBS")
+ (MODEM . "Modem")
+ (ISDN . "ISDN")
+ (PCS . "PCS")))
+
+(defconst jabber-vcard-email-types '((HOME . "Home")
+ (WORK . "Work")
+ (INTERNET . "Internet")
+ (X400 . "X400")
+ (PREF . "Preferred")))
+
+(defconst jabber-vcard-address-types '((HOME . "Home")
+ (WORK . "Work")
+ (POSTAL . "Postal")
+ (PARCEL . "Parcel")
+ (DOM . "Domestic")
+ (INTL . "International")
+ (PREF . "Preferred")))
+
+(defconst jabber-vcard-address-fields '((POBOX . "Post box")
+ (EXTADD . "Ext. address")
+ (STREET . "Street")
+ (LOCALITY . "Locality")
+ (REGION . "Region")
+ (PCODE . "Post code")
+ (CTRY . "Country")))
+
+(defun jabber-vcard-display (xml-data)
+ "Display received vcard."
+ (let ((parsed (jabber-vcard-parse (jabber-iq-query xml-data))))
+ (dolist (simple-field jabber-vcard-fields)
+ (let ((field (assq (car simple-field) parsed)))
+ (when field
+ (insert (cdr simple-field))
+ (indent-to 20)
+ (insert (cdr field) "\n"))))
+
+ (let ((names (cdr (assq 'N parsed))))
+ (when names
+ (insert "\n")
+ (dolist (name-field jabber-vcard-name-fields)
+ (let ((field (assq (car name-field) names)))
+ (when field
+ (insert (cdr name-field))
+ (indent-to 20)
+ (insert (cdr field) "\n"))))))
+
+ (let ((email-addresses (cdr (assq 'EMAIL parsed))))
+ (when email-addresses
+ (insert "\n")
+ (insert (jabber-propertize "E-mail addresses:\n"
+ 'face 'jabber-title-medium))
+ (dolist (email email-addresses)
+ (insert (mapconcat (lambda (type)
+ (cdr (assq type jabber-vcard-email-types)))
+ (car email)
+ " "))
+ (insert ": " (cdr email) "\n"))))
+
+ (let ((phone-numbers (cdr (assq 'TEL parsed))))
+ (when phone-numbers
+ (insert "\n")
+ (insert (jabber-propertize "Phone numbers:\n"
+ 'face 'jabber-title-medium))
+ (dolist (number phone-numbers)
+ (insert (mapconcat (lambda (type)
+ (cdr (assq type jabber-vcard-phone-types)))
+ (car number)
+ " "))
+ (insert ": " (cdr number) "\n"))))
+
+ (let ((addresses (cdr (assq 'ADR parsed))))
+ (when addresses
+ (insert "\n")
+ (insert (jabber-propertize "Addresses:\n"
+ 'face 'jabber-title-medium))
+ (dolist (address addresses)
+ (insert (jabber-propertize
+ (mapconcat (lambda (type)
+ (cdr (assq type jabber-vcard-address-types)))
+ (car address)
+ " ")
+ 'face 'jabber-title-small))
+ (insert "\n")
+ (dolist (address-field jabber-vcard-address-fields)
+ (let ((field (assq (car address-field) address)))
+ (when field
+ (insert (cdr address-field))
+ (indent-to 20)
+ (insert (cdr field) "\n")))))))))
+
+(defun jabber-vcard-do-edit (xml-data closure-data)
+ (let ((parsed (jabber-vcard-parse (jabber-iq-query xml-data))))
+ (with-current-buffer (get-buffer-create "Edit vcard")
+ (jabber-init-widget-buffer nil)
+
+ (dolist (simple-field jabber-vcard-fields)
+ (widget-insert (cdr simple-field))
+ (indent-to 15)
+ (let ((default-value (cdr (assq (car simple-field) parsed))))
+ (push (cons (car simple-field)
+ (widget-create 'editable-field (or default-value "")))
+ jabber-widget-alist)))
+
+ (widget-insert "\n")
+ (push (cons 'N
+ (widget-create
+ '(set :tag "Decomposited name"
+ (cons :tag "Prefix" :format "%t: %v" (const :format "" PREFIX) (string :format "%v"))
+ (cons :tag "Given name" :format "%t: %v" (const :format "" GIVEN) (string :format "%v"))
+ (cons :tag "Middle name" :format "%t: %v" (const :format "" MIDDLE) (string :format "%v"))
+ (cons :tag "Family name" :format "%t: %v" (const :format "" FAMILY) (string :format "%v"))
+ (cons :tag "Suffix" :format "%t: %v" (const :format "" SUFFIX) (string :format "%v")))
+ :value (cdr (assq 'N parsed))))
+ jabber-widget-alist)
+
+ (widget-insert "\n")
+ (push (cons 'ADR
+ (widget-create
+ '(repeat :tag "Postal addresses"
+ (cons
+ :tag "Address"
+ (set :tag "Type"
+ (const :tag "Home" HOME)
+ (const :tag "Work" WORK)
+ (const :tag "Postal" POSTAL)
+ (const :tag "Parcel" PARCEL)
+ (const :tag "Domestic" DOM)
+ (const :tag "International" INTL)
+ (const :tag "Preferred" PREF))
+ (set
+ :tag "Address"
+ (cons :tag "Post box" :format "%t: %v"
+ (const :format "" POBOX) (string :format "%v"))
+ (cons :tag "Ext. address" :format "%t: %v"
+ (const :format "" EXTADD) (string :format "%v"))
+ (cons :tag "Street" :format "%t: %v"
+ (const :format "" STREET) (string :format "%v"))
+ (cons :tag "Locality" :format "%t: %v"
+ (const :format "" LOCALITY) (string :format "%v"))
+ (cons :tag "Region" :format "%t: %v"
+ (const :format "" REGION) (string :format "%v"))
+ (cons :tag "Post code" :format "%t: %v"
+ (const :format "" PCODE) (string :format "%v"))
+ (cons :tag "Country" :format "%t: %v"
+ (const :format "" CTRY) (string :format "%v")))))
+ :value (cdr (assq 'ADR parsed))))
+ jabber-widget-alist)
+
+ (widget-insert "\n")
+ (push (cons 'TEL
+ (widget-create
+ '(repeat :tag "Phone numbers"
+ (cons :tag "Number"
+ (set :tag "Type"
+ (const :tag "Home" HOME)
+ (const :tag "Work" WORK)
+ (const :tag "Voice" VOICE)
+ (const :tag "Fax" FAX)
+ (const :tag "Pager" PAGER)
+ (const :tag "Message" MSG)
+ (const :tag "Cell phone" CELL)
+ (const :tag "Video" VIDEO)
+ (const :tag "BBS" BBS)
+ (const :tag "Modem" MODEM)
+ (const :tag "ISDN" ISDN)
+ (const :tag "PCS" PCS))
+ (string :tag "Number")))
+ :value (cdr (assq 'TEL parsed))))
+ jabber-widget-alist)
+
+ (widget-insert "\n")
+ (push (cons 'EMAIL
+ (widget-create
+ '(repeat :tag "E-mail addresses"
+ (cons :tag "Address"
+ (set :tag "Type"
+ (const :tag "Home" HOME)
+ (const :tag "Work" WORK)
+ (const :tag "Internet" INTERNET)
+ (const :tag "X400" X400)
+ (const :tag "Preferred" PREF))
+ (string :tag "Address")))
+ :value (cdr (assq 'EMAIL parsed))))
+ jabber-widget-alist)
+
+ (widget-insert "\n")
+ (widget-create 'push-button :notify #'jabber-vcard-submit "Submit")
+
+ (widget-setup)
+ (widget-minor-mode 1)
+ (switch-to-buffer (current-buffer)))))
+
+(defun jabber-vcard-submit (&rest ignore)
+ (jabber-send-iq nil
+ "set"
+ (jabber-vcard-reassemble
+ (mapcar (lambda (entry)
+ (cons (car entry) (widget-value (cdr entry))))
+ jabber-widget-alist))
+ #'jabber-report-success "Changing vCard"
+ #'jabber-report-success "Changing vCard"))
+
+(provide 'jabber-vcard)
+;; arch-tag: 65B95E9C-63BD-11D9-94A9-000A95C2FCD0
diff --git a/jabber-version.el b/jabber-version.el
new file mode 100644
index 0000000..c59ca9f
--- /dev/null
+++ b/jabber-version.el
@@ -0,0 +1,72 @@
+;; jabber-version.el - version reporting by JEP-0092
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-iq)
+(require 'jabber-util)
+
+(defconst jabber-version "0.6.1pre"
+ "version returned to those who query us")
+
+(add-to-list 'jabber-jid-info-menu
+ (cons "Request software version" 'jabber-get-version))
+(defun jabber-get-version (to)
+ "Request software version"
+ (interactive (list (jabber-read-jid-completing "Request version of: ")))
+ ;; XXX: you will not get any result unless you add the resource to the JID.
+ (jabber-send-iq to
+ "get"
+ '(query ((xmlns . "jabber:iq:version")))
+ #'jabber-process-data #'jabber-process-version
+ #'jabber-process-data "Version request failed"))
+
+;; called by jabber-process-data
+(defun jabber-process-version (xml-data)
+ "Handle results from jabber:iq:version requests."
+
+ (let ((query (jabber-iq-query xml-data)))
+ (dolist (x '((name . "Name:\t\t") (version . "Version:\t") (os . "OS:\t\t")))
+ (let ((data (car (jabber-xml-node-children (car (jabber-xml-get-children query (car x)))))))
+ (when data
+ (insert (cdr x) data "\n"))))))
+
+(add-to-list 'jabber-iq-get-xmlns-alist (cons "jabber:iq:version" 'jabber-return-version))
+(add-to-list 'jabber-advertised-features "jabber:iq:version")
+(defun jabber-return-version (xml-data)
+ "Return client version as defined in JEP-0092. Sender and ID are
+determined from the incoming packet passed in XML-DATA."
+ ;; Things we might check: does this iq message really have type='get' and
+ ;; exactly one child, namely query with xmlns='jabber:iq:version'?
+ ;; Then again, jabber-process-iq should take care of that.
+ (let ((to (jabber-xml-get-attribute xml-data 'from))
+ (id (jabber-xml-get-attribute xml-data 'id)))
+ (jabber-send-iq to "result"
+ `(query ((xmlns . "jabber:iq:version"))
+ (name () "jabber.el")
+ (version () ,jabber-version)
+ ;; Booting... /vmemacs.el
+ ;; Shamelessly stolen from someone's sig.
+ (os () ,(jabber-escape-xml (emacs-version))))
+ nil nil nil nil
+ id)))
+
+(provide 'jabber-version)
+
+;;; arch-tag: 2051dbe7-01b5-401e-bd8a-fe24afb88e1e
diff --git a/jabber-watch.el b/jabber-watch.el
new file mode 100644
index 0000000..ccf14d0
--- /dev/null
+++ b/jabber-watch.el
@@ -0,0 +1,72 @@
+;; jabber-watch.el - get notified when certain persons go online
+
+;; Copyright (C) 2004 - Mathias Dahl
+;; Copyright (C) 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'jabber-util)
+
+(defvar jabber-watch-alist nil
+ "Alist of buddies for which an extra notification should be sent
+when they come online, with comment strings as values.")
+
+(defun jabber-presence-watch (who oldstatus newstatus
+ statustext proposed-alert)
+ "Checks if one of your extra-important buddies comes online and
+sends a message if that happens. The buddies are stored in
+`jabber-watch-alist' and are added and removed by calling
+`jabber-watch-add' and `jabber-watch-remove.'"
+ ;; check that buddy was previously offline and now online
+ (if (and (null oldstatus)
+ (not (null newstatus)))
+ (let ((entry (assq who jabber-watch-alist)))
+ (when entry
+ ;; Give an intrusive message. With a window system,
+ ;; that's easy.
+ (if window-system
+ (message-box "%s%s" proposed-alert
+ (if (cdr entry) (format ": %s" (cdr entry)) ""))
+ ;; Without a window system, yes-or-no-p should be
+ ;; sufficient.
+ (while (not
+ (yes-or-no-p (format "%s%s Got that? " proposed-alert
+ (if (cdr entry) (format ": %s" (cdr entry)) ""))))))))))
+
+(defun jabber-watch-add (buddy &optional comment)
+ (interactive (list (jabber-read-jid-completing "Add buddy to watch list: ")
+ (read-string "Comment: ")))
+ (unless (memq 'jabber-presence-watch jabber-presence-hooks)
+ (error "jabber-presence-watch is not in jabber-presence-hooks"))
+ (add-to-list 'jabber-watch-alist (cons
+ (jabber-jid-symbol buddy)
+ (and (not (zerop (length comment)))
+ comment))))
+
+(defun jabber-watch-remove (buddy)
+ (interactive
+ (list (jabber-read-jid-completing "Remove buddy from watch list: "
+ (or (mapcar 'car jabber-watch-alist)
+ (error "Watch list is empty"))
+ t)))
+ (setq jabber-watch-alist
+ (delq (assq (jabber-jid-symbol buddy) jabber-watch-alist)
+ jabber-watch-alist)))
+
+(provide 'jabber-watch)
+
+;; arch-tag: c27299d8-019e-44b5-9529-d67b8682be23
diff --git a/jabber-widget.el b/jabber-widget.el
new file mode 100644
index 0000000..8a58c26
--- /dev/null
+++ b/jabber-widget.el
@@ -0,0 +1,322 @@
+;; jabber-widget.el - display various kinds of forms
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'widget)
+(require 'wid-edit)
+(require 'jabber-util)
+(require 'jabber-disco)
+
+(defvar jabber-widget-alist nil
+ "Alist of widgets currently used")
+
+(defvar jabber-form-type nil
+ "Type of form. One of:
+'x-data, jabber:x:data
+'register, as used in jabber:iq:register and jabber:iq:search")
+
+(defvar jabber-submit-to nil
+ "JID of the entity to which form data is to be sent")
+
+(add-to-list 'jabber-advertised-features "jabber:x:data")
+
+(defun jabber-init-widget-buffer (submit-to)
+ "Setup buffer-local variables for widgets."
+ (make-local-variable 'jabber-widget-alist)
+ (make-local-variable 'jabber-submit-to)
+ (setq jabber-widget-alist nil)
+ (setq jabber-submit-to submit-to)
+ (setq buffer-read-only nil)
+ ;; XXX: This is because data from other queries would otherwise be
+ ;; appended to this buffer, which would fail since widget buffers
+ ;; are read-only... or something like that. Maybe there's a
+ ;; better way.
+ (rename-uniquely))
+
+(defun jabber-render-register-form (query)
+ "Display widgets from <query/> element in jabber:iq:{register,search} namespace."
+ (make-local-variable 'jabber-widget-alist)
+ (setq jabber-widget-alist nil)
+ (make-local-variable 'jabber-form-type)
+ (setq jabber-form-type 'register)
+
+ (if (jabber-xml-get-children query 'instructions)
+ (widget-insert "Instructions: " (car (jabber-xml-node-children (car (jabber-xml-get-children query 'instructions)))) "\n"))
+ (if (jabber-xml-get-children query 'registered)
+ (widget-insert "You are already registered. You can change your details here.\n"))
+ (widget-insert "\n")
+
+ (let ((possible-fields
+ ;; taken from JEP-0077
+ '((username . "Username")
+ (nick . "Nickname")
+ (password . "Password")
+ (name . "Full name")
+ (first . "First name")
+ (last . "Last name")
+ (email . "E-mail")
+ (address . "Address")
+ (city . "City")
+ (state . "State")
+ (zip . "Zip")
+ (phone . "Telephone")
+ (url . "Web page")
+ (date . "Birth date"))))
+ (dolist (field (jabber-xml-node-children query))
+ (let ((entry (assq (jabber-xml-node-name field) possible-fields)))
+ (when entry
+ (widget-insert (cdr entry) "\t")
+ ;; Special case: when registering a new account, the default
+ ;; username is the one specified in jabber-username. Things
+ ;; will break if the user changes that name, though...
+ (let ((default-value (if (and jabber-register-p
+ (eq (jabber-xml-node-name field) 'username))
+ jabber-username
+ "")))
+ (setq jabber-widget-alist
+ (cons
+ (cons (car entry)
+ (widget-create 'editable-field
+ :secret (if (eq (car entry) 'password)
+ ?* nil)
+ (or (car (jabber-xml-node-children
+ field)) default-value)))
+ jabber-widget-alist)))
+ (widget-insert "\n"))))))
+
+(defun jabber-parse-register-form ()
+ "Return children of a <query/> tag containing information entered in the widgets of the current buffer."
+ (mapcar
+ (lambda (widget-cons)
+ (list (car widget-cons)
+ nil
+ (widget-value (cdr widget-cons))))
+ jabber-widget-alist))
+
+(defun jabber-render-xdata-form (x &optional defaults)
+ "Display widgets from <x/> element in jabber:x:data namespace.
+DEFAULTS is an alist associating variable names with default values.
+DEFAULTS takes precedence over values specified in the form."
+ (make-local-variable 'jabber-widget-alist)
+ (setq jabber-widget-alist nil)
+ (make-local-variable 'jabber-form-type)
+ (setq jabber-form-type 'xdata)
+
+ (let ((title (car (jabber-xml-node-children (car (jabber-xml-get-children x 'title))))))
+ (if (stringp title)
+ (widget-insert (jabber-propertize title 'face 'jabber-title-medium) "\n\n")))
+ (let ((instructions (car (jabber-xml-node-children (car (jabber-xml-get-children x 'instructions))))))
+ (if (stringp instructions)
+ (widget-insert "Instructions: " instructions "\n\n")))
+
+ (dolist (field (jabber-xml-get-children x 'field))
+ (let* ((var (jabber-xml-get-attribute field 'var))
+ (label (jabber-xml-get-attribute field 'label))
+ (type (jabber-xml-get-attribute field 'type))
+ (required (jabber-xml-get-children field 'required))
+ (values (jabber-xml-get-children field 'value))
+ (options (jabber-xml-get-children field 'option))
+ (desc (car (jabber-xml-get-children field 'desc)))
+ (default-value (assoc var defaults)))
+ ;; "required" not implemented yet
+
+ (cond
+ ((string= type "fixed")
+ (widget-insert (car (jabber-xml-node-children (car values)))))
+
+ ((string= type "text-multi")
+ (if (or label var)
+ (widget-insert (or label var) ":\n"))
+ (push (cons (cons var type)
+ (widget-create 'text (or (cdr default-value)
+ (mapconcat #'(lambda (val)
+ (car (jabber-xml-node-children val)))
+ values "\n")
+ "")))
+ jabber-widget-alist))
+
+ ((string= type "list-single")
+ (if (or label var)
+ (widget-insert (or label var) ":\n"))
+ (push (cons (cons var type)
+ (apply 'widget-create
+ 'radio-button-choice
+ :value (or (cdr default-value)
+ (car (xml-node-children (car values))))
+ (mapcar (lambda (option)
+ `(item :tag ,(jabber-xml-get-attribute option 'label)
+ :value ,(car (jabber-xml-node-children (car (jabber-xml-get-children option 'value))))))
+ options)))
+ jabber-widget-alist))
+
+ ((string= type "boolean")
+ (push (cons (cons var type)
+ (widget-create 'checkbox
+ :tag (or label var)
+ :value (if default-value
+ (cdr default-value)
+ (not (null
+ (member (car (xml-node-children (car values))) '("1" "true")))))))
+ jabber-widget-alist)
+ (if (or label var)
+ (widget-insert " " (or label var) "\n")))
+
+ (t ; in particular including text-single and text-private
+ (if (or label var)
+ (widget-insert (or label var) ": "))
+ (setq jabber-widget-alist
+ (cons
+ (cons (cons var type)
+ (widget-create 'editable-field
+ :secret (if (string= type "text-private") ?* nil)
+ (or (cdr default-value)
+ (car (jabber-xml-node-children (car values)))
+ "")))
+ jabber-widget-alist))))
+ (when (and desc (car (jabber-xml-node-children desc)))
+ (widget-insert "\n" (car (jabber-xml-node-children desc))))
+ (widget-insert "\n"))))
+
+(defun jabber-parse-xdata-form ()
+ "Return an <x/> tag containing information entered in the widgets of the current buffer."
+ `(x ((xmlns . "jabber:x:data")
+ (type . "submit"))
+ ,@(mapcar
+ (lambda (widget-cons)
+ (let ((values (jabber-xdata-value-convert (widget-value (cdr widget-cons)) (cdar widget-cons))))
+ ;; empty fields are not included
+ (when values
+ `(field ((var . ,(caar widget-cons)))
+ ,@(mapcar
+ (lambda (value)
+ (list 'value nil value))
+ values)))))
+ jabber-widget-alist)))
+
+(defun jabber-xdata-value-convert (value type)
+ "Convert VALUE from form used by widget library to form required by JEP-0004.
+Return a list of strings, each of which to be included as cdata in a <value/> tag."
+ (cond
+ ((string= type "boolean")
+ (if value (list "1") (list "0")))
+ ((string= type "text-multi")
+ (split-string value "[\n\r]"))
+ (t ; in particular including text-single, text-private and list-single
+ (if (zerop (length value))
+ nil
+ (list value)))))
+
+(defun jabber-render-xdata-search-results (xdata)
+ "Render search results in x:data form."
+
+ (let ((title (car (jabber-xml-get-children xdata 'title))))
+ (when title
+ (insert (jabber-propertize (car (jabber-xml-node-children title)) 'face 'jabber-title-medium) "\n")))
+
+ (if (jabber-xml-get-children xdata 'reported)
+ (jabber-render-xdata-search-results-multi xdata)
+ (jabber-render-xdata-search-results-single xdata)))
+
+(defun jabber-render-xdata-search-results-multi (xdata)
+ "Render multi-record search results."
+ (let (fields
+ (jid-fields 0))
+ (let ((reported (car (jabber-xml-get-children xdata 'reported)))
+ (column 0))
+ (dolist (field (jabber-xml-get-children reported 'field))
+ (let (width)
+ ;; Clever algorithm for estimating width based on field type goes here.
+ (setq width 20)
+
+ (setq fields
+ (append
+ fields
+ (list (cons (jabber-xml-get-attribute field 'var)
+ (list 'label (jabber-xml-get-attribute field 'label)
+ 'type (jabber-xml-get-attribute field 'type)
+ 'column column)))))
+ (setq column (+ column width))
+ (if (string= (jabber-xml-get-attribute field 'type) "jid-single")
+ (setq jid-fields (1+ jid-fields))))))
+
+ (dolist (field-cons fields)
+ (indent-to (plist-get (cdr field-cons) 'column) 1)
+ (insert (jabber-propertize (plist-get (cdr field-cons) 'label) 'face 'bold)))
+ (insert "\n\n")
+
+ ;; Now, the items
+ (dolist (item (jabber-xml-get-children xdata 'item))
+
+ (let ((start-of-line (point))
+ jid)
+
+ ;; The following code assumes that the order of the <field/>s in each
+ ;; <item/> is the same as in the <reported/> tag.
+ (dolist (field (jabber-xml-get-children item 'field))
+ (let ((field-plist (cdr (assoc (jabber-xml-get-attribute field 'var) fields)))
+ (value (car (jabber-xml-node-children (car (jabber-xml-get-children field 'value))))))
+
+ (indent-to (plist-get field-plist 'column) 1)
+
+ ;; Absent values are sometimes "", sometimes nil. insert
+ ;; doesn't like nil.
+ (when value
+ ;; If there is only one JID field, let the whole row
+ ;; have the jabber-jid property. If there are many JID
+ ;; fields, the string belonging to each field has that
+ ;; property.
+ (if (string= (plist-get field-plist 'type) "jid-single")
+ (if (not (eq jid-fields 1))
+ (insert (jabber-propertize value 'jabber-jid value))
+ (setq jid value)
+ (insert value))
+ (insert value)))))
+
+ (if jid
+ (put-text-property start-of-line (point)
+ 'jabber-jid jid))
+ (insert "\n")))))
+
+(defun jabber-render-xdata-search-results-single (xdata)
+ "Render single-record search results."
+ (dolist (field (jabber-xml-get-children xdata 'field))
+ (let ((label (jabber-xml-get-attribute field 'label))
+ (type (jabber-xml-get-attribute field 'type))
+ (values (mapcar #'(lambda (val)
+ (car (jabber-xml-node-children val)))
+ (jabber-xml-get-children field 'value))))
+ ;; XXX: consider type
+ (insert (jabber-propertize (concat label ": ") 'face 'bold))
+ (indent-to 30)
+ (insert (apply #'concat values) "\n"))))
+
+(defun jabber-xdata-formtype (x)
+ "Return the form type of the xdata form in X, by JEP-0068.
+Return nil if no form type is specified."
+ (catch 'found-formtype
+ (dolist (field (jabber-xml-get-children x 'field))
+ (when (and (string= (jabber-xml-get-attribute field 'var) "FORM_TYPE")
+ (string= (jabber-xml-get-attribute field 'type) "hidden"))
+ (throw 'found-formtype (car (jabber-xml-node-children
+ (car (jabber-xml-get-children field 'value)))))))))
+
+(provide 'jabber-widget)
+
+;;; arch-tag: da3312f3-1970-41d5-a974-14b8d76156b8
diff --git a/jabber-xmessage.el b/jabber-xmessage.el
new file mode 100644
index 0000000..f15334d
--- /dev/null
+++ b/jabber-xmessage.el
@@ -0,0 +1,30 @@
+;; jabber-xmessage.el - emacs-jabber interface to xmessage
+
+;; Copyright (C) 2005 - Mario Domenech Goulart
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(defun jabber-xmessage-display-message (message)
+ "Displays MESSAGE using the xmessage program."
+ (let ((process-connection-type nil))
+ (start-process "xmessage" nil "xmessage" message)))
+
+(define-jabber-alert xmessage "Display a message using the xmessage program."
+ 'jabber-xmessage-display-message)
+
+(provide 'jabber-xmessage)
+;; arch-tag: 10A74D00-5D2C-11D9-A294-000A95C2FCD0
diff --git a/jabber-xml.el b/jabber-xml.el
new file mode 100644
index 0000000..f782133
--- /dev/null
+++ b/jabber-xml.el
@@ -0,0 +1,206 @@
+;; jabber-xml.el - XML functions
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+(require 'xml)
+(require 'jabber-util)
+
+(defun jabber-escape-xml (str)
+ "escape strings for xml"
+ (if (stringp str)
+ (let ((newstr (concat str)))
+ ;; Form feeds might appear in code you copy, etc. Nevertheless,
+ ;; it's invalid XML.
+ (setq newstr (jabber-replace-in-string newstr "\f" "\n"))
+ ;; Other control characters are also illegal, except for
+ ;; tab, CR, and LF.
+ (setq newstr (jabber-replace-in-string newstr "[\000-\010\013\014\016-\037]" " "))
+ (setq newstr (jabber-replace-in-string newstr "&" "&amp;"))
+ (setq newstr (jabber-replace-in-string newstr "<" "&lt;"))
+ (setq newstr (jabber-replace-in-string newstr ">" "&gt;"))
+ (setq newstr (jabber-replace-in-string newstr "'" "&apos;"))
+ (setq newstr (jabber-replace-in-string newstr "\"" "&quot;"))
+ newstr)
+ str))
+
+(defun jabber-unescape-xml (str)
+ "unescape xml strings"
+ ;; Eventually this can be done with `xml-substitute-special', but the
+ ;; version in xml.el of GNU Emacs 21.3 is buggy.
+ (if (stringp str)
+ (let ((newstr str))
+ (setq newstr (jabber-replace-in-string newstr "&quot;" "\""))
+ (setq newstr (jabber-replace-in-string newstr "&apos;" "'"))
+ (setq newstr (jabber-replace-in-string newstr "&gt;" ">"))
+ (setq newstr (jabber-replace-in-string newstr "&lt;" "<"))
+ (setq newstr (jabber-replace-in-string newstr "&amp;" "&"))
+ newstr)
+ str))
+
+(defun jabber-sexp2xml (sexp)
+ "converts an SEXP in the format (tagname ((attribute-name . attribute-value)...) children...) and converts it to well-formatted xml."
+ (cond
+ ((stringp sexp)
+ sexp)
+ ((listp (car sexp))
+ (let ((xml ""))
+ (dolist (tag sexp)
+ (setq xml (concat xml (jabber-sexp2xml tag))))
+ xml))
+ ;; work around bug in old versions of xml.el, where ("") can appear
+ ;; as children of a node
+ ((and (consp sexp)
+ (stringp (car sexp))
+ (zerop (length (car sexp))))
+ "")
+ (t
+ (let ((xml ""))
+ (setq xml (concat "<"
+ (symbol-name (car sexp))))
+ (dolist (attr (cadr sexp))
+ (if (consp attr)
+ (setq xml (concat xml
+ (format " %s='%s'"
+ (symbol-name (car attr))
+ (cdr attr))))))
+ (if (cddr sexp)
+ (progn
+ (setq xml (concat xml ">"))
+ (dolist (child (cddr sexp))
+ (setq xml (concat xml
+ (jabber-sexp2xml child))))
+ (setq xml (concat xml
+ "</"
+ (symbol-name (car sexp))
+ ">")))
+ (setq xml (concat xml
+ "/>")))
+ xml))))
+
+(defun jabber-xml-skip-tag-forward ()
+ "Skip to end of tag or matching closing tag if present.
+Return t iff after a closing tag, otherwise throws an 'unfinished
+tag with value nil.
+
+The version of `sgml-skip-tag-forward' in Emacs 21 isn't good
+enough for us."
+ (skip-chars-forward "^<")
+ (if (not (looking-at "<\\([^ \t\n/>]+\\)\\([ \t\n]+[^=]+='[^']*'\\|[ \t\n]+[^=]+=\"[^\"]*\"\\)*"))
+ (throw 'unfinished nil)
+ (let ((node-name (match-string 1)))
+ (goto-char (match-end 0))
+ (cond
+ ((looking-at "/>")
+ (goto-char (match-end 0))
+ t)
+ ((looking-at ">")
+ (forward-char 1)
+ (loop
+ do (skip-chars-forward "^<")
+ until (looking-at (regexp-quote (concat "</" node-name ">")))
+ do (jabber-xml-skip-tag-forward))
+ (goto-char (match-end 0))
+ t)
+ (t
+ (throw 'unfinished nil))))))
+
+(defsubst jabber-xml-node-name (node)
+ "Return the tag associated with NODE.
+The tag is a lower-case symbol."
+ (if (listp node) (car node)))
+
+(defsubst jabber-xml-node-attributes (node)
+ "Return the list of attributes of NODE.
+The list can be nil."
+ (if (listp node) (nth 1 node)))
+
+(defsubst jabber-xml-node-children (node)
+ "Return the list of children of NODE.
+This is a list of nodes, and it can be nil."
+ (let ((children (cddr node)))
+ ;; Work around a bug in early versions of xml.el
+ (if (equal children '(("")))
+ nil
+ children)))
+
+(defun jabber-xml-get-children (node child-name)
+ "Return the children of NODE whose tag is CHILD-NAME.
+CHILD-NAME should be a lower case symbol."
+ (let ((match ()))
+ (dolist (child (jabber-xml-node-children node))
+ (if child
+ (if (equal (jabber-xml-node-name child) child-name)
+ (push child match))))
+ (nreverse match)))
+
+;; `xml-get-attribute' returns "" if the attribute is not found, which
+;; is not very useful. Therefore, we use `xml-get-attribute-or-nil'
+;; if present, or emulate its behavior.
+(if (fboundp 'xml-get-attribute-or-nil)
+ (defalias 'jabber-xml-get-attribute 'xml-get-attribute-or-nil)
+ (defsubst jabber-xml-get-attribute (node attribute)
+ "Get from NODE the value of ATTRIBUTE.
+Return nil if the attribute was not found."
+ (let ((result (xml-get-attribute node attribute)))
+ (and (> (length result) 0) result))))
+
+(defun jabber-xml-path (xml-data path)
+ "Find sub-node of XML-DATA according to PATH.
+PATH is a vaguely XPath-inspired list. Each element can be:
+
+a symbol go to first child node with this node name
+cons cell car is string containing namespace URI,
+ cdr is string containing node name. Find
+ first matching child node.
+any string character data of this node"
+ (let ((node xml-data))
+ (while (and path node)
+ (let ((step (car path)))
+ (cond
+ ((symbolp step)
+ (setq node (car (jabber-xml-get-children node step))))
+ ((consp step)
+ ;; This will be easier with namespace-aware use
+ ;; of xml.el. It will also be more correct.
+ ;; Now, it only matches explicit namespace declarations.
+ (setq node
+ (dolist (x (jabber-xml-get-children node (intern (cdr step))))
+ (when (string= (jabber-xml-get-attribute x 'xmlns)
+ (car step))
+ (return x)))))
+ ((stringp step)
+ (setq node (car (jabber-xml-node-children node)))
+ (unless (stringp node)
+ (setq node nil)))
+ (t
+ (error "Unknown path step: %s" step))))
+ (setq path (cdr path)))
+ node))
+
+(defmacro jabber-xml-let-attributes (attributes xml-data &rest body)
+ "Bind variables to the same-name attribute values in XML-DATA."
+ `(let ,(mapcar #'(lambda (attr)
+ (list attr `(jabber-xml-get-attribute ,xml-data ',attr)))
+ attributes)
+ ,@body))
+
+(provide 'jabber-xml)
+
+;;; arch-tag: ca206e65-7026-4ee8-9af2-ff6a9c5af98a
diff --git a/jabber.el b/jabber.el
new file mode 100644
index 0000000..9dc1066
--- /dev/null
+++ b/jabber.el
@@ -0,0 +1,183 @@
+;; jabber.el - a minimal jabber client
+
+;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
+;; Copyright (C) 2003, 2004 - Magnus Henoch - mange@freemail.hu
+
+;; SSL - Support, mostly inspired by Gnus
+;; Copyright (C) 2005 - Georg Lehner - jorge@magma.com.ni
+
+;; This file is a part of jabber.el.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+;;; load Unicode tables if this needed
+(when (and (featurep 'xemacs) (not (emacs-version>= 21 5 5)))
+ (require 'un-define))
+
+;;; these customize fields should come first
+(defgroup jabber nil "Jabber instant messaging"
+ :group 'applications)
+
+(defcustom jabber-username "emacs"
+ "jabber username (user part of JID)"
+ :type 'string
+ :group 'jabber)
+
+(defcustom jabber-server "magaf.org"
+ "jabber server (domain part of JID)"
+ :type 'string
+ :group 'jabber)
+
+(defcustom jabber-password nil
+ "jabber password"
+ :type '(radio (const :tag "Prompt for password" nil)
+ (string :tag "Save password in .emacs"))
+ :group 'jabber)
+
+(defcustom jabber-resource "emacs"
+ "jabber resource"
+ :type 'string
+ :group 'jabber)
+
+(defcustom jabber-default-show ""
+ "default show state"
+ :type '(choice (const :tag "Online" "")
+ (const :tag "Chatty" "chat")
+ (const :tag "Away" "away")
+ (const :tag "Extended away" "xa")
+ (const :tag "Do not disturb" "dnd"))
+ :group 'jabber)
+
+(defcustom jabber-default-status ""
+ "default status string"
+ :type 'string
+ :group 'jabber)
+
+(defcustom jabber-default-priority 10
+ "default priority"
+ :type 'integer
+ :group 'jabber)
+
+(defcustom jabber-nickname jabber-username
+ "jabber nickname, used in chat buffer prompts and as default groupchat nickname."
+ :type 'string
+ :group 'jabber)
+
+;;; guess internal dependencies!
+(require 'jabber-util)
+(require 'jabber-menu)
+(require 'jabber-xml)
+(require 'jabber-conn)
+(require 'jabber-core)
+(require 'jabber-logon)
+(require 'jabber-roster)
+(require 'jabber-presence)
+(require 'jabber-alert)
+(require 'jabber-chat)
+(require 'jabber-disco)
+(require 'jabber-iq)
+(require 'jabber-widget)
+(require 'jabber-register)
+(require 'jabber-search)
+(require 'jabber-browse)
+(require 'jabber-muc)
+(require 'jabber-version)
+(require 'jabber-ahc-presence)
+(require 'jabber-modeline)
+(require 'jabber-keepalive)
+(require 'jabber-watch)
+(require 'jabber-activity)
+(require 'jabber-vcard)
+(require 'jabber-events)
+
+;; XXX: automate this some time
+(autoload 'jabber-export-roster "jabber-export"
+ "Create buffer from which roster can be exported to a file."
+ t)
+(autoload 'jabber-import-roster "jabber-export"
+ "Create buffer for roster import from FILE."
+ t)
+
+(defvar *jabber-current-status* ""
+ "the users current presence staus")
+
+(defvar *jabber-current-show* ""
+ "the users current presence show")
+
+(defvar *jabber-current-priority* 10
+ "the user's current priority")
+
+(defvar *jabber-status-history* nil
+ "history of status messages")
+
+(defgroup jabber-faces nil "faces for displaying jabber instant messaging"
+ :group 'jabber)
+
+(defface jabber-title-small
+ '((t (:weight bold :width semi-expanded :height 1.0 :inherit variable-pitch)))
+ "face for small titles"
+ :group 'jabber-faces)
+
+(defface jabber-title-medium
+ '((t (:weight bold :width expanded :height 2.0 :inherit variable-pitch)))
+ "face for medium titles"
+ :group 'jabber-faces)
+
+(defface jabber-title-large
+ '((t (:weight bold :width ultra-expanded :height 3.0 :inherit variable-pitch)))
+ "face for large titles"
+ :group 'jabber-faces)
+
+(defgroup jabber-debug nil "debugging options"
+ :group 'jabber)
+
+(defcustom jabber-debug-log-xml nil
+ "log all XML i/o in *-jabber-xml-log-*"
+ :type 'boolean
+ :group 'jabber-debug)
+
+(defconst jabber-presence-faces
+ '(("" . jabber-roster-user-online)
+ ("away" . jabber-roster-user-away)
+ ("xa" . jabber-roster-user-xa)
+ ("dnd" . jabber-roster-user-dnd)
+ ("chat" . jabber-roster-user-chatty)
+ ("error" . jabber-roster-user-error)
+ (nil . jabber-roster-user-offline))
+ "Mapping from presence types to faces")
+
+(defconst jabber-presence-strings
+ '(("" . "Online")
+ ("away" . "Away")
+ ("xa" . "Extended Away")
+ ("dnd" . "Do not Disturb")
+ ("chat" . "Chatty")
+ ("error" . "Error")
+ (nil . "Offline"))
+ "Mapping from presence types to readable strings")
+
+(defun jabber-customize ()
+ "customize jabber options"
+ (interactive)
+ (customize-group 'jabber))
+
+(defun jabber-info ()
+ "open jabber.el manual"
+ (interactive)
+ (info "jabber"))
+
+(provide 'jabber)
+
+;;; arch-tag: 5145153e-4d19-4dc2-800c-b1282feb155d
diff --git a/jabber.texi b/jabber.texi
new file mode 100644
index 0000000..77dde78
--- /dev/null
+++ b/jabber.texi
@@ -0,0 +1,1863 @@
+\input texinfo @c -*-texinfo-*-
+@c %**start of header
+@setfilename jabber.info
+@settitle jabber.el manual 0.7
+@c %**end of header
+
+@dircategory Emacs
+@direntry
+* jabber.el: (jabber). Emacs Jabber client
+@end direntry
+
+@copying
+This manual is for jabber.el, version 0.7.
+
+Copyright @copyright{} 2004, 2005 Magnus Henoch, Tom Berger.
+
+@quotation
+Permission is granted to make and distribute verbatim copies or
+modified versions of this manual, provided the copyright notice and
+this permission notice are preserved on all copies.
+@end quotation
+@end copying
+
+@titlepage
+@title jabber.el
+@subtitle where Emacs and Jabber meet
+@author by Magnus Henoch and Tom Berger
+
+@page
+@vskip 0pt plus 1filll
+@insertcopying
+@end titlepage
+
+@contents
+
+@ifnottex
+@node Top, Introduction, (dir), (dir)
+@top jabber.el manual
+
+@insertcopying
+
+@end ifnottex
+
+@menu
+* Introduction::
+* Basic operation::
+* Groupchat::
+* Services::
+* Personal information::
+* Useful features::
+* Message history::
+* Message events::
+* Roster import and export::
+* Customization::
+* Hacking and extending::
+* Protocol support::
+* Concept index::
+* Function index::
+* Variable index::
+@end menu
+
+
+@node Introduction, Basic operation, Top, Top
+@chapter Introduction
+
+jabber.el is a Jabber client running under Emacs. For more
+information on the open-protocol instant messaging network Jabber,
+please visit @uref{http://www.jabber.org}.
+
+As a Jabber client, jabber.el is mostly just a face in the crowd,
+except that it uses buffers where GUI clients have windows. There is
+a roster buffer, and to chat with someone you open a chat buffer, and
+there are browse buffers (increasingly inexactly named) for
+interaction with servers and services. Then again, jabber.el delivers
+excellent console performance and customizable hooks (if you have
+speech synthesizer software, hook it up to your presence alerts).
+
+jabber.el does not yet support STARTTLS, GPG, sending and receiving
+roster items, and various other things.
+
+@menu
+* Contact::
+@end menu
+
+@node Contact, , Introduction, Introduction
+@section Contact
+
+jabber.el is developed by Tom Berger (e-mail
+@email{object@@intellectronica.net}, JID @code{object@@jabber.org.uk}) and
+Magnus Henoch (e-mail @email{mange@@freemail.hu}, JID
+@code{legoscia@@jabber.cd.chalmers.se}). There is a web page at
+@uref{http://intellectronica.net/emacs-jabber/}, and a Sourceforge
+project page at @uref{http://sourceforge.net/projects/emacs-jabber}.
+
+@node Basic operation, Groupchat, Introduction, Top
+@chapter Basic operation
+
+This chapter is intended as an introduction to basic usage of
+jabber.el. If you have used Jabber before and are familiar with the
+terminology, you might find it a bit too basic --- in that case, just
+skim it, making sure to pick up the commands mentioned.
+
+There are a handful of global keybindings for common commands. They
+start with @kbd{C-x C-j}, and you can get a list of them by typing
+@kbd{C-x C-j C-h}.
+
+@menu
+* Connecting::
+* Chatting::
+* Presence::
+* Presence subscription::
+* Roster buffer::
+@end menu
+
+@node Connecting, Chatting, Basic operation, Basic operation
+@section Connecting
+
+@findex jabber-connect
+@findex jabber-disconnect
+
+@cindex Connecting
+@cindex Registering an account
+
+I'll assume that you have already successfully installed jabber.el; if
+not, consult the @file{README} file. Also, make sure you have
+@code{(require 'jabber)} in your @file{.emacs}.
+
+Now, type @kbd{M-x jabber-customize}. This brings up a customize
+buffer for jabber.el. The most important variables to customize are
+@code{jabber-username} and
+@code{jabber-server}.@footnote{@xref{Connection settings}, for other
+things you might have to change.} Save your changes, and type
+@kbd{M-x jabber-connect} to connect.
+
+If you do not yet have a Jabber account, you can register one. Enter
+your desired username for @code{jabber-username} and the server you wish
+to use for @code{jabber-server}, save, and type @kbd{C-u M-x
+jabber-connect} or @kbd{C-u C-x C-j C-c}. If the server supports
+in-band registration, you will be presented with a registration form to
+fill out and send. There the username you chose will be prefilled.
+Don't change it, otherwise jabber.el will be confused.
+
+If you successfully connect, jabber.el will download your roster and
+display it in a buffer called @code{*-jabber-*}.
+
+By default, you will appear as ``online'' to your contacts. To change
+this to e.g. ``away'', type @kbd{M-x jabber-send-presence}.
+@xref{Presence}, for more information.
+
+To disconnect, type @kbd{M-x jabber-disconnect} or @kbd{C-x C-j C-d}.
+
+@node Chatting, Presence, Connecting, Basic operation
+@section Chatting
+
+@cindex Chatting
+@findex jabber-chat-with
+
+There are several ways to open a chat buffer. The shortest way is to
+put point over the person you want to chat with in the roster display
+and hit RET.
+
+You can also use menus to access commands. In the roster display, you
+can access several menus through keystrokes or mouse clicks. You can
+bring one big menu up by pressing the second mouse button, or you can
+bring up the ``chat menu'' by typing @kbd{C-c C-c}. If you do the
+latter while point is on a roster entry, that entry will be the
+default value when you are asked for whom to chat with.
+
+You can also use the function @code{jabber-chat-with}, which is what the
+menu item is bound to. This function is bound to @kbd{C-x C-j C-j} in
+the global keymap.
+
+Now, try opening a chat with someone. A buffer named
+@code{*-jabber-chat-:-@var{person}-*} will be created and selected.
+Type your message at the end of the buffer, and hit @kbd{RET} to send
+it. To include a newline in your message, use @kbd{C-j}.
+
+@node Presence, Presence subscription, Chatting, Basic operation
+@section Presence
+
+@cindex Presence
+@cindex Sending presence
+@findex jabber-send-presence
+@findex jabber-send-default-presence
+@vindex jabber-default-show
+@vindex jabber-default-status
+@vindex jabber-default-priority
+
+``Presence'' is the Jabber term for letting other people know that you
+are online, and additionally how ``available'' you are. There are
+three elements to presence: availability status (called ``show''),
+status message, and priority.
+
+Your show status may either be empty (meaning simply ``online''), or
+one of @code{away}, @code{xa}, @code{dnd} and @code{chat}, meaning
+``away'', ``extended away'' (i.e. away for an extended period), ``do
+not disturb'', and ``free for chat'', respectively. This information
+is available to everyone subscribing to your presence, but technically
+it does not restrict anyone's actions. You can chat with people even
+if you claim to be away.
+
+The status message is a short text complementing your show status,
+such as ``at home'', ``working'', ``phone'', ``playing games'' or
+whatever you want. It is sent to everyone subscribing to your
+presence, but not all clients prominently display it to the user.
+
+The priority is only interesting if you are running more than one
+Jabber client at a time accessing the same account. In that case,
+messages sent to you without an indication of which client to send to
+are sent to the client with the highest priority.
+
+To set your presence, use the function @code{jabber-send-presence}.
+It can be called both interactively and in Lisp code. For the latter
+case, use something like @code{(jabber-send-presence "away" "idle for
+10 minutes" 10)}.
+
+By default, jabber.el sets your presence when you connect. If you
+want it not to do that, remove @code{jabber-send-default-presence}
+from @code{jabber-post-connect-hook}. If you want to change the
+presence that is sent, change the variables
+@code{jabber-default-show}, @code{jabber-default-status} and
+@code{jabber-default-priority}.
+
+With jabber.el, you can set your presence remotely. @xref{Ad-Hoc Commands}.
+
+@node Presence subscription, Roster buffer, Presence, Basic operation
+@section Presence subscription
+
+@cindex Presence subscription
+@findex jabber-send-subscription-request
+
+Having permission to view the presence status of a person is called
+@dfn{subscribing to his presence}. Presence subscription between two
+persons can be asymmetric.
+
+When jabber.el receives a presence subscription request, it will
+present it to you in an alert requiring immediate response, and offer
+you to send a subscription request back to that person.
+
+To request subscription to someone, type @kbd{M-x
+jabber-send-subscription-request}. You will be prompted for the JID
+to send it to. This command can also be accessed through the Roster
+menu, by typing @kbd{C-c C-r} in the roster buffer. After that, you
+will probably want to give the contact a more readable name. The
+command for that is @code{jabber-roster-change}, which is also available
+in the Roster menu.
+
+@node Roster buffer, , Presence subscription, Basic operation
+@section The roster buffer
+
+@cindex Roster buffer
+@cindex Menus
+@cindex Key bindings
+@findex jabber-display-roster
+
+The roster buffer is called @code{*-jabber-*}. It simply contains a
+list of the contacts on your roster.
+
+In the roster buffer, any command which requires a JID will default to
+the JID under point when called. These commands can be called through
+either keyboard menus or mouse menus. To open a menu with the mouse,
+simply press the second mouse button over the JID in
+question.@footnote{For some reason, mouse menus don't work in XEmacs.
+Patches welcome.} This will bring up a menu with all available
+actions. The keyboard menus are split into categories: Chat, Roster,
+Information, MUC (Multi-User Chat, or groupchat) and Services, opened
+by @kbd{C-c C-c}, @kbd{C-c C-r}, @kbd{C-c C-i}, @kbd{C-c C-m} and
+@kbd{C-c C-s}, respectively.
+
+A list of keybindings is displayed at the top of the roster buffer.
+You can turn it off by setting @code{jabber-roster-show-bindings} to
+nil.
+
+You can call @code{jabber-display-roster} to redisplay your roster
+according to changed preferences (@pxref{Customizing the roster
+buffer}). This will not refetch your roster from the server.
+Refetching the roster is usually not needed, since updates are pushed
+to clients automatically.
+
+You can choose not to have the roster updated automatically on
+presence changes (@pxref{Presence alerts}). In that case, you need to
+call @code{jabber-display-roster} manually.
+
+@node Groupchat, Services, Basic operation, Top
+@chapter Groupchat
+
+@cindex Groupchat
+@cindex MUC
+@findex jabber-groupchat-join
+@findex jabber-groupchat-leave
+@findex jabber-groupchat-get-config
+@findex jabber-muc-names
+@findex jabber-muc-set-role
+@findex jabber-muc-set-topic
+
+The groupchat menu can be accessed by typing @kbd{C-c C-m} in the
+roster buffer. You can also type the commands directly, as will be
+shown here.
+
+To join a groupchat, type @kbd{M-x jabber-groupchat-join}. You will
+be prompted for the groupchat to join, and your nickname in the
+groupchat. This nickname doesn't need to have any correlation to your
+JID; in fact, groupchats are usually (but not always) configured such
+that only moderators can see your JID. You can change your nickname
+with @kbd{M-x jabber-muc-nick}.
+
+Groupchat messages will be displayed in a buffer called
+@code{*-jabber-groupchat-:-@var{groupchat}-*}. It works much like the
+chat buffer.
+
+To change the topic of a groupchat, type @kbd{M-x
+jabber-muc-set-topic}.
+
+To leave a groupchat, type @kbd{M-x jabber-groupchat-leave}.
+
+If you are the owner of a groupchat, you can change its configuration
+by typing @kbd{M-x jabber-groupchat-get-config}. A configuration form
+will be rendered in new buffer.
+
+To see which people are in a groupchat, type @kbd{M-x
+jabber-muc-names}. This gives a list of nicknames, ``roles'',
+``affiliations'', and possibly JIDs. @xref{MUC Administration}, for
+the meaning of roles and affiliations.
+
+
+@menu
+* Automation::
+* Invitations::
+* Private messages::
+* MUC Administration::
+@end menu
+
+@node Automation, Invitations, Groupchat, Groupchat
+@section Automation
+
+@vindex jabber-muc-default-nicknames
+@vindex jabber-muc-autojoin
+@findex jabber-muc-autojoin
+@cindex Default MUC nickname
+@cindex Autojoin chat rooms
+
+You can select a default nickname by setting @code{jabber-nickname}.
+Additionally, you can set different nicknames for different groups, by
+customizing @code{jabber-muc-default-nicknames}. There you specify
+the JID of the group, and your preferred nickname.
+
+If you want to automatically join certain rooms when connecting, you
+can set @code{jabber-muc-autojoin} to a list containing the JIDs of
+the rooms you want to enter. To disable this feature, remove
+@code{jabber-muc-autojoin} from @code{jabber-post-connect-hook}.
+
+@node Invitations, Private messages, Automation, Groupchat
+@section Invitations
+
+@cindex Invitations
+@findex jabber-muc-invite
+
+You can invite someone to a groupchat with @kbd{M-x jabber-muc-invite}
+(also available in the MUC menu). Pay attention to the order of the
+arguments --- as both users and rooms are just JIDs, it is technically
+possible to invite a room to a user, but that's probably not what you
+want.
+
+When you receive an invitation, it appears in the chat buffer along
+with two buttons, ``Accept'' and ``Decline''. Pressing ``Accept''
+enters the room, as you would expect. Pressing ``Decline'' gives you
+an opportunity to state the reason why you're not joining.
+
+@node Private messages, MUC Administration, Invitations, Groupchat
+@section Private messages
+
+@cindex Private MUC messages
+@findex jabber-muc-private
+
+You can open a private chat with a participant in a chat room with
+@kbd{M-x jabber-muc-private} (or by using the MUC menu). This creates
+a buffer with the name
+@code{*-jabber-muc-priv-@var{group}-@var{nickname}-*} (customizable by
+@code{jabber-muc-private-buffer-format}), which behaves mostly like an
+ordinary chat buffer. This buffer will also be created if someone
+sends a private message to you.
+
+Private MUC messages use the same alerts as normal chat messages.
+@xref{Message alerts}.
+
+@node MUC Administration, , Private messages, Groupchat
+@section Administration
+
+The possible roles are:
+
+@table @samp
+@item moderator
+Has voice, can change other people's roles.
+
+@item participant
+Has voice.
+
+@item visitor
+Doesn't have voice (can't send messages to everyone, but can send
+private messages)
+
+@item none
+Not in room.
+@end table
+
+Affiliations are:
+
+@table @samp
+@item owner
+Can destroy room, appoint admins, make people members, ban people.
+
+@item admin
+Can make people members or ban people.
+
+@item member
+Different privileges depending on room configuration (room may be
+members-only, or grant voice only to members)
+
+@item none
+Rights depend on room configuration.
+
+@item outcast
+Banned from the room
+@end table
+
+If you have moderator privileges, you can change the role of a
+participant with @kbd{M-x jabber-muc-set-role}. Kicking means setting
+the role to ``none''. Granting and revoking voice are ``participant''
+and ``visitor'', respectively. ``moderator'' gives moderator
+privileges, obviously. The role of a participant is ephemeral, and
+disappears when leaving the room.
+
+If you have admin or owner privileges, you can change the affiliation of
+a user. Affiliation is persistent, and based on JIDs. Depending of
+your affiliation and the MUC implementation, you might not be allowed to
+perform all kinds of changes, and maybe not in one step.
+
+
+@node Services, Personal information, Groupchat, Top
+@chapter Services
+
+@cindex Browse buffers
+
+Not every Jabber entity is a physical person. There are many
+automatic entities, called servers, services, components, agents,
+transports and other names. The use of these is described here.
+
+The functions described in this chapter use @dfn{browse buffers}.
+Browse buffers are named @code{*-jabber-browse-:-@var{service}-*},
+sometimes with a numerical suffix. The different menus have the same
+keybindings as in the roster buffer, and if you call a function
+operating on a JID while point is over a JID, that JID will be the
+default value, so you don't have to type it or copy it yourself.
+
+@menu
+* Service discovery and browsing::
+* Registering::
+* Searching::
+* Ad-Hoc Commands::
+@end menu
+
+@node Service discovery and browsing, Registering, Services, Services
+@section Service discovery and browsing
+
+@cindex Service discovery
+@cindex Browsing
+@findex jabber-get-browse
+@findex jabber-get-disco-items
+@findex jabber-get-disco-info
+
+To find services you want to use, you need to discover them first.
+This can be done with either service discovery or browsing. Service
+discovery is the newer and preferred protocol, while browsing is still
+used by much software. The use of both is very similar.
+
+The most common use of service discovery is to browse your home
+server, to see what services are provided locally. Note, however,
+that this is no restriction; you can use services from all over the
+network.
+
+For service discovery there are two commands,
+@code{jabber-get-disco-items} and @code{jabber-get-disco-info},
+depending on whether you want information about that specific JID or
+about services related to it, respectively. To start browsing, type
+@kbd{M-x jabber-get-browse} and enter the JID you want to browse.
+
+These commands can be accessed from the Info menu, which is opened by
+typing @kbd{C-c C-i}.
+
+If you think that the interface to service discovery is awkward and
+should be replaced with something better, you are completely right.
+
+@node Registering, Searching, Service discovery and browsing, Services
+@section Registering
+
+@cindex Registration
+@cindex Cancelling registration
+@cindex Changing password
+@cindex Gateway registration
+@cindex Password change
+@findex jabber-get-register
+
+Some services, in particular user directories and gateways to legacy
+IM systems, require registration. To register with such a service,
+either type @kbd{M-x jabber-get-register} or select it from the
+Service menu, which is opened by typing @kbd{C-c C-s}. You have to
+know the service's JID, possibly from service discovery.
+(@pxref{Service discovery and browsing})
+
+This is also the way to change your registration details, e.g. your
+password --- just ask to register with that service again. To change
+the password of your Jabber account, ask to register with your Jabber
+server.
+
+Please note that any passwords sent in this way will be sent in
+cleartext to your Jabber server, unless you have enabled SSL encryption
+(@pxref{Connection settings}), and possibly sent in cleartext from your
+server to the server hosting the service.
+
+jabber.el will then request a registration form from that service. If
+for some reason the service does not answer (maybe network problems,
+or some services neither support registration nor report errors about
+that) that will be the last thing you saw about it. jabber.el will
+not report timeout errors, but rather simply wait until you shut it
+down.
+
+Once the response arrives, the form will be rendered in a browse
+buffer. Just fill out the fields, and hit Submit. You will receive
+confirmation of your registration in the echo area.
+
+To cancel an existing registration (and also for cancelling your
+Jabber account, if you sent a registration request to your server),
+hit Cancel. The unregistration will be confirmed in the echo area.
+
+@node Searching, Ad-Hoc Commands, Registering, Services
+@section Searching
+
+@cindex Searching
+@findex jabber-get-search
+
+Some services, notably user directories and gateways to legacy IM
+systems, allow searching. Searching in Jabber generally means
+searching for someone's JID, but the protocol is general enough to
+support most databases.
+
+To search a service, either type @kbd{M-x jabber-get-search} or select
+it from the Service menu, which is opened by typing @kbd{C-c C-s}.
+
+Just like with registration, this command sends a request for a search
+form, and displays it if and when the response arrives. Enter your
+search and submit it. Search results will be displayed in a different
+browse buffer.
+
+@node Ad-Hoc Commands, , Searching, Services
+@section Ad-Hoc Commands
+
+@cindex Ad-Hoc Commands
+@findex jabber-ahc-get-list
+@findex jabber-ahc-execute-command
+
+jabber.el supports a subset of JEP-0050, the standard for Ad-Hoc
+Commands. As the name implies, this can be used for just about
+anything. It has been used for remote-controlling clients (e.g. Psi),
+and administering services (e.g. PyMSNt).
+
+Currently, jabber.el uses ad-hoc commands for setting presence remotely.
+If you realize that you forgot to set your client to ``away'' with a low
+priority, you can do it remotely.@footnote{Most Jabber servers also
+support kicking a client off the net by logging in with another client
+with exactly the same resource.}
+
+The commands for executing ad-hoc commands are available under the
+Service menu, which is opened by typing @kbd{C-c C-s}.
+
+To find which commands are available, run ``Request command-list''
+(@code{jabber-ahc-get-list}).@footnote{This is the same thing as a
+disco items request to the node
+@code{http://jabber.org/protocol/commands}.}
+
+To run a command from the list, put point over it and run ``Execute
+command'' (@code{jabber-ahc-execute-command}), accepting the defaults
+for JID and node. (If you already know those, you could of course
+enter them yourself) The form you get should hopefully be
+self-explanatory.
+
+@node Personal information, Useful features, Services, Top
+@chapter Personal information
+
+@cindex vCard
+@findex jabber-vcard-get
+@findex jabber-vcard-edit
+
+The Jabber way of handling personal information (name, addresses,
+phone numbers, etc) is ``vCards'' encoded in XML. You can get
+information about a user by running @kbd{M-x jabber-vcard-get}, and
+you can edit your own information by running @kbd{M-x
+jabber-vcard-edit}.
+
+The form for editing your information can be slightly confusing ---
+you are allowed to enter any number of addresses, phone numbers and
+e-mail addresses, each of which has a set of orthogonal properties.
+You can add and remove items with the [INS] and [DEL] buttons,
+respectively.
+
+@node Useful features, Message history, Personal information, Top
+@chapter Useful features
+
+jabber.el includes a number of features meant to improve the user
+interface and do other useful things.
+
+@menu
+* Modeline status::
+* Keepalive::
+* Tracking activity::
+* Watch buddies::
+* Spell checking::
+@end menu
+
+@node Modeline status, Keepalive, Useful features, Useful features
+@section Modeline status
+
+@cindex modeline
+@findex jabber-mode-line-mode
+@vindex jabber-mode-line-mode
+@vindex jabber-mode-line-compact
+
+By typing @kbd{M-x jabber-mode-line-mode} you toggle display of some
+status in mode lines. The information is your own presence status,
+and some numbers showing the status of your roster contacts. By
+default, there are three numbers, for ``online'' (chatty and online),
+``away'' (away, extended away and do not disturb) and offline
+contacts.
+
+If you set @code{jabber-mode-line-compact} to nil, you get a complete
+breakdown of presence status. That gives you six numbers indicating
+the number of chatty, online, away, extended away, dnd, and offline
+contacts, respectively.
+
+@node Keepalive, Tracking activity, Modeline status, Useful features
+@section Keepalive
+
+@cindex keepalive
+@findex jabber-keepalive-start
+@findex jabber-keepalive-stop
+@vindex jabber-keepalive-interval
+@vindex jabber-keepalive-timeout
+
+Sometimes network connections are lost without you noticing. This is
+especially true with Jabber, as it is quite reasonable to keep the
+connection open for a long time without either sending or receiving
+any data.
+
+If you want to detect a lost connection earlier, you can use the
+keepalive functions. Type @kbd{M-x jabber-keepalive-start} to start
+it, and @kbd{M-x jabber-keepalive-stop} to stop it.
+
+These functions work by asking your server for the time once in a
+while (by default every ten minutes), and considering the connection
+lost if the server doesn't answer within reasonable time (by default
+20 seconds).
+
+You can customize the interval and the timeout with the variables
+@code{jabber-keepalive-interval} and @code{jabber-keepalive-timeout},
+respectively.
+
+@node Tracking activity, Watch buddies, Keepalive, Useful features
+@section Tracking activity
+
+@cindex Activity
+@findex jabber-activity-mode
+@vindex jabber-activity-make-strings
+@vindex jabber-activity-query-unread
+@vindex jabber-activity-count-in-title
+@vindex jabber-activity-count-in-title-format
+
+When you're working on something important you might want to delay
+responding to incoming messages. However, when you're done working,
+will you remember them? If you're anything like me, you'll have a lot
+of buffers in your Emacs session, and a jabber chat buffer can easily
+get lost.
+
+When you type @kbd{M-x jabber-activity-mode} Emacs starts keeping
+track of the buddies which have messaged you since last you visited
+their buffer, and will display them in mode line. As soon as you
+visit their buffer they disappear from the mode line, indicating that
+you've read their message.
+
+If your mode line fills over because of these notifications, you can
+customize @code{jabber-activity-make-strings} to shorten them to the
+shortest possibly unambiguous form.
+
+If you try to exit Emacs while you still have unread messages, you
+will be notified and asked about this. If you don't like that, set
+@code{jabber-activity-query-unread} to nil.
+
+If you want to display the number of unread buffers in the frame title,
+set @code{jabber-activity-count-in-title} to t. The format of the
+number can be changed through
+@code{jabber-activity-count-in-title-format}.
+
+For complete customizability, write a hook function for
+@code{jabber-activity-update-hook}. From that function, you can take
+action based on @code{jabber-activity-jids},
+@code{jabber-activity-mode-string}, and
+@code{jabber-activity-count-string}.
+
+@node Watch buddies, Spell checking, Tracking activity, Useful features
+@section Watch buddies
+
+@cindex watch
+@findex jabber-watch-add
+@findex jabber-watch-remove
+
+Sometimes you might be waiting for a certain person to come online,
+and you don't want that occasion to get lost in the noise. To get an
+obtrusive message when that happens, type @kbd{M-x jabber-watch-add}
+and select the person in question. You can enter a comment, to
+remember why you added the watch.
+
+You will get a message whenever that person goes from offline to
+online. jabber.el will remember this for the rest of your Emacs
+session (it's not saved to disk, though), but if you want to get rid
+of it, type @kbd{M-x jabber-watch-remove}.
+
+@node Spell checking, , Watch buddies, Useful features
+@section Spell checking
+
+You can activate spell checking in a chat buffer with @kbd{M-x
+flyspell-mode}. It will check only what you are currently writing, not
+what you receive or what you have already sent. You may want to add
+@code{flyspell-mode} to @code{jabber-chat-mode-hook}.
+
+For more information about Emacs spell checking, @xref{Spelling, ,
+Checking and Correcting Spelling, emacs, GNU Emacs Manual}.
+
+@node Message history, Message events, Useful features, Top
+@chapter Message history
+
+@cindex history
+@cindex backlog
+@cindex rotation
+@vindex jabber-history-enabled
+@vindex jabber-global-history-filename
+@vindex jabber-use-global-history
+@vindex jabber-history-dir
+@vindex jabber-history-enable-rotation
+@vindex jabber-history-size-limit
+@vindex jabber-backlog-number
+@vindex jabber-backlog-days
+
+If you want a record of messages sent and received, set
+@code{jabber-history-enabled} to t. By default all messages to will
+be saved to a global history file specified by
+@code{jabber-global-history-filename}
+(@file{~/.jabber_global_message_log} by default). If you prefer to
+store your chats' history in per-contact files, you can set the
+@code{jabber-use-global-history} variable to @code{nil}. When using
+per-contact history, files are named by the contact JID and saved
+under the directory specified by the variable
+@code{jabber-history-dir} (default is @file{~/.emacs-jabber}).
+
+There is no facility for reading old messages yet, but just reading
+the file as text should be enough for many purposes.
+
+When you open a new chat buffer and have entries in your history file,
+the last few messages you recently exchanged with the contact in
+question will be inserted. You can control how many messages with
+@code{jabber-backlog-number} (by default 10), and how old messages
+with @code{jabber-backlog-days} (by default 3 days).
+
+If you worry about your history file(s) size, you can enable history
+rotation feature by setting the variable
+@code{jabber-history-enable-rotation} to @code{t} (default is
+@code{nil}). This feature ``rotates'' your history files according to
+the following rule: When @code{jabber-history-size-limit} (in
+kilobytes) is reached, the history file is renamed to
+<history-file>-<number>, where <number> is 1 or the smallest number
+after the last rotation. For example, suppose you set the
+@code{jabber-history-size-limit} variable to 512 and you chat with
+your buddy foo@@jabber.server using the per-contact strategy to store
+history files. So, when the history file (@file{foo@@jabber-server})
+reaches 512K bytes, it will be renamed to @file{foo@@jabber-server-1}
+and @file{foo@@jabber-server} will be set empty. Next time
+@file{foo@@jabber-server} grows to 512K bytes, it will be saved as
+@file{foo@@jabber-server-2} and so on. Although the example was
+presented with the per-contact history file strategy, history rotation
+works for both per-contact and global history logging strategies.
+
+
+@node Message events, Roster import and export, Message history, Top
+@chapter Message events
+
+@cindex composing
+@cindex delivered
+@cindex displayed
+@vindex jabber-events-request-these
+@vindex jabber-events-confirm-delivered
+@vindex jabber-events-confirm-displayed
+@vindex jabber-events-confirm-composing
+
+In the status line of the chat buffer, you can sometimes see
+notifications about the progress of the message you just sent. These
+states are possible:
+
+@itemize @bullet
+@item
+Delivered to offline storage (the user will receive it on next logon)
+
+@item
+Delivered to user's client (but not necessarily displayed)
+
+@item
+Displayed to user
+
+@item
+User is composing a reply
+
+@end itemize
+
+The first state is only reported by servers; the other three are
+reported by clients. jabber.el can report all three of them, and can
+display all four; not all clients support all states, though.
+
+If you don't want jabber.el to send out this information about you, set
+the variables @code{jabber-events-confirm-delivered},
+@code{jabber-events-confirm-displayed}, and/or
+@code{jabber-events-confirm-composing} to nil. You can make jabber.el
+not to request such information by customizing
+@code{jabber-events-request-these}.
+
+@node Roster import and export, Customization, Message events, Top
+@chapter Roster import and export
+
+@findex jabber-export-roster
+@findex jabber-import-roster
+@cindex export roster
+@cindex import roster
+
+Your roster is saved on the Jabber server, and usually not in the
+client. However, you might want to save the roster to a file anyway.
+The most common reason for this is probably to copy it to another
+account.
+
+To export your roster to a file, type @kbd{M-x jabber-export-roster}.
+A buffer will appear in which you can edit the data to be exported.
+Changes done in that buffer will not affect your real roster.
+
+To import your roster from a file, type @kbd{M-x jabber-import-roster}.
+You will be able to edit the data before importing it. Items not in the
+roster will be added; items in the roster will be modified to match
+imported data. Subscriptions will be updated.
+
+The format of the roster files is the XML used by roster pushes in the
+XMPP protocol, in UTF-8 encoding.
+
+@node Customization, Hacking and extending, Roster import and export, Top
+@chapter Customization
+
+@findex jabber-customize
+@cindex Customization
+
+jabber.el is intended to be customizable for many tastes. After all,
+this is Emacs. To open a customization buffer for jabber.el, type
+@kbd{M-x jabber-customize}.
+
+@menu
+* Account settings::
+* Connection settings::
+* Miscellaneous settings::
+* Customizing the roster buffer::
+* Customizing the chat buffer::
+* Customizing alerts::
+* Hooks::
+* Debug options::
+@end menu
+
+@node Account settings, Connection settings, Customization, Customization
+@section Account settings
+
+@vindex jabber-username
+@vindex jabber-server
+@vindex jabber-password
+@vindex jabber-resource
+@vindex jabber-default-priority
+@vindex jabber-nickname
+
+@code{jabber-username} is the username part of your JID.
+
+@code{jabber-server} is the JID of your server, i.e. the hostname part
+of your JID. This is usually, but not necessarily, the same as the
+hostname of the server.
+
+@code{jabber-password} is your password. You have the option to set
+it here, in which case it will be stored in cleartext in your
+@file{.emacs} file. If this is set to @code{nil}, you will be prompted for
+your password every time you connect.
+
+@code{jabber-resource} is the resource you want to log in under. This
+only matters if you are connected to the same account from different
+clients or different computers, since each connection must have a
+unique resource. You might want to set this to your hostname.
+
+@code{jabber-default-priority} is the default priority sent with your
+presence. Regardless of what you have here, you can change your
+priority during a session with @code{jabber-send-presence}.
+@xref{Presence}, for more information on priority.
+
+@code{jabber-nickname} is your default nickname for groupchats.
+
+@node Connection settings, Miscellaneous settings, Account settings, Customization
+@section Connection settings
+
+@vindex jabber-network-server
+@vindex jabber-port
+@vindex jabber-connection-type
+@vindex jabber-connection-ssl-program
+@cindex SSL
+@cindex TLS
+
+@code{jabber-network-server} is the hostname or IP address of your
+server. If it is set to @code{nil}, jabber.el will use the name in
+@code{jabber-server}.
+
+@code{jabber-port} is the TCP port of the server to connect to. If
+@code{nil}, the default port is selected based on the chosen
+connection method.
+
+@code{jabber-connection-type} specifies what kind of connection to
+use. @code{network} means normal unencrypted network connection
+(usually on port 5222), and @code{ssl} means encrypted connection
+through GnuTLS or OpenSSL (port 5223). You can change the settings of
+the encryption program through @kbd{M-x customize-group RET tls} or
+@kbd{M-x customize-group RET ssl}, respectively.
+
+By default, GnuTLS will be used if the @code{tls} library is available,
+and if that fails, OpenSSL will be used if the @code{ssl} library is
+available. You can force the use of either program by setting
+@code{jabber-connection-ssl-program} to @code{gnutls} or @code{openssl},
+respectively.
+
+@node Miscellaneous settings, Customizing the roster buffer, Connection settings, Customization
+@section Miscellaneous settings
+
+@findex jabber-menu
+@cindex Menus
+
+If you want a Jabber menu on the menu bar with some common commands,
+type @kbd{M-x jabber-menu}. You can remove it again with @kbd{C-u M-x
+jabber-menu}. Unfortunately, this cannot be changed through Customize
+settings, so you need to add @code{(jabber-menu)} to your @file{.emacs}
+to enable it permanently.
+
+@node Customizing the roster buffer, Customizing the chat buffer, Miscellaneous settings, Customization
+@section Customizing the roster buffer
+
+@vindex jabber-roster-sort-functions
+@vindex jabber-sort-order
+@vindex jabber-show-resources
+@vindex jabber-roster-line-format
+@vindex jabber-resource-line-format
+@vindex jabber-roster-buffer
+@vindex jabber-roster-show-bindings
+
+@code{jabber-roster-sort-functions} controls how roster items are
+sorted. By default, contacts are sorted first by presence, and then
+alphabetically by displayed name.
+
+@code{jabber-sort-order} controls how roster items are sorted by
+presence. It is a list containing strings corresponding to show
+status (@pxref{Presence}) or @code{nil}, which represents offline.
+
+@code{jabber-show-resources} controls when your contacts' resources
+are shown in the roster buffer. The default is to show resources when
+a contact has more than one connected resource.
+
+@code{jabber-roster-line-format} specifies how the entry for each
+contact looks. It is a string where some characters are special if
+preceded by a percent sign:
+
+@table @code
+@item %c
+"*" if the contact is connected, or " " if not
+@item %n
+Nickname of contact, or JID if no nickname
+@item %j
+Bare JID of contact (without resource)
+@item %r
+Highest-priority resource of contact
+@item %s
+Availability of contact as string ("Online", "Away" etc)
+@item %S
+Status string specified by contact
+@end table
+
+@code{jabber-resource-line-format} is nearly identical, except that
+the values correspond to the values of the resource in question, and
+that the @code{%p} escape is available, which inserts the priority of
+the resource.
+
+@code{jabber-roster-buffer} specifies the name of the roster buffer.
+If you change this, the new name will be used the next time you
+connect.
+
+@code{jabber-roster-show-bindings} controls whether to show a list of
+keybindings at the top of the roster buffer. You need to run @kbd{M-x
+jabber-display-roster} after changing this variable to update the display.
+
+@node Customizing the chat buffer, Customizing alerts, Customizing the roster buffer, Customization
+@section Customizing the chat buffer
+
+@vindex jabber-chat-time-format
+@vindex jabber-chat-delayed-time-format
+@vindex jabber-print-rare-time
+@vindex jabber-rare-time-format
+@vindex jabber-chat-local-prompt-format
+@vindex jabber-chat-foreign-prompt-format
+@vindex jabber-chat-prompt-local
+@vindex jabber-chat-prompt-foreign
+@vindex jabber-chat-header-line-format
+@vindex jabber-muc-header-line-format
+@vindex jabber-chat-fill-long-lines
+@cindex Chat buffer
+@cindex Timestamps
+
+You can customize the look of the prompts in the chat buffer. There
+are separate settings for local (i.e. your own messages) and foreign
+prompts.
+
+@code{jabber-chat-prompt-local} and @code{jabber-chat-prompt-foreign}
+determine the faces used for the prompts.
+
+@code{jabber-chat-local-prompt-format} and
+@code{jabber-chat-foreign-prompt-format} determine what text is
+displayed in the prompts. They are format strings, with the following
+special sequences defined:
+
+@table @code
+@item %t
+The time when the message was sent or received
+@item %n
+The nickname of the user. For the foreign prompt, this is the name of
+the contact in the roster, or the JID if no name set. For the local
+prompt, this is the username part of your JID.
+@item %j
+The bare JID of the user
+@end table
+
+@code{jabber-chat-time-format} defines how @code{%t} shows time. Its
+format is identical to that passed to @code{format-time-string}.
+@xref{Time Conversion, , Time Conversion, elisp, GNU Emacs Lisp
+Reference Manual}.
+
+@code{jabber-chat-delayed-time-format} is used instead of
+@code{jabber-chat-time-format} for delayed messages (messages sent while
+you were offline, or fetched from history). This way you can have short
+timestamps everywhere except where you need long ones. You can always
+see the complete timestamp in a tooltip by hovering over the prompt with
+the mouse.
+
+By default, timestamps are printed in the chat buffer every hour (at
+``rare'' times). This can be toggled with
+@code{jabber-print-rare-time}. You can customize the displayed time by
+setting @code{jabber-rare-time-format}. Rare timestamps will be printed
+whenever time formatted by that format string would change.
+
+You can also customize the header line of chat buffers, by modifying
+the variable @code{jabber-chat-header-line-format}. The format of
+that variable is the same as that of @code{mode-line-format} and
+@code{header-line-format}. @xref{Mode Line Format, , Mode-Line
+Format, elisp, GNU Emacs Lisp Reference Manual}. For MUC buffers,
+@code{jabber-muc-header-line-format} is used instead.
+
+The variable @code{jabber-chat-fill-long-lines} controls whether long
+lines in the chat buffer are filled.
+
+@node Customizing alerts, Hooks, Customizing the chat buffer, Customization
+@section Customizing alerts
+
+@cindex Alert hooks
+@findex define-jabber-alert
+
+When an event happens (currently including presence changes, incoming
+messages, and completed queries) you will usually want to be
+notified. Since tastes in this area vary wildly, these alerts are
+implemented as hooks, so you can choose which ones you want, or write
+your own if none fit.
+
+Actually, if you don't want to write your own, stop reading this
+section and just read @ref{Standard alerts}.
+
+Many kinds of alerts consist in displaying a text message through a
+certain mechanism. This text message is provided by a function which
+you can rewrite or replace. If this function returns @code{nil}, no
+message is displayed, and non-textual alerts refrain from action.
+
+If you want to write alert hooks that do nothing except displaying the
+supplied message in some way, use the macro
+@code{define-jabber-alert}. For example, if @var{foo} is a function
+that takes a string as an argument, write
+@example
+(define-jabber-alert foo
+ "Display a message in a fooish way"
+ 'foo)
+@end example
+and all details will be taken care of for you.
+
+The hooks take different arguments depending on category. However,
+they all have in common that the last argument is the result of the
+message function. The message function for each category takes the
+same arguments as the corresponding hooks, except for that last
+argument.
+
+Alert hook contributions are very welcome. You can send them to the
+mailing list, or to the Sourceforge patch tracker.
+
+Alert hooks are meant for optional UI things, that are subject to
+varying user tastes, and that can be toggled by simply adding or
+removing the function from/to the hook. For other things, there are
+corresponding general hooks, that are defvars instead of defcustoms, and
+that are to be managed by Lisp code. They have the same name as the
+alert hooks minus the @code{-alert} part,
+i.e. @code{jabber-message-hooks} vs @code{jabber-alert-message-hooks},
+etc.
+
+@menu
+* Standard alerts::
+* Presence alerts::
+* Message alerts::
+* MUC alerts::
+* Info alerts::
+@end menu
+
+@node Standard alerts, Presence alerts, Customizing alerts, Customizing alerts
+@subsection Standard alerts
+
+@cindex alerts
+@cindex scroll
+
+Eight alerts are already written for all four alert categories. These
+all obey the result from the corresponding message function.
+
+The @code{beep} alerts simply sound the terminal bell by calling
+@code{ding}. They are disabled by default.
+
+The @code{echo} alerts display a message in the echo area by calling
+@code{message}. They are enabled by default.
+
+The @code{switch} alerts switch to the buffer where the event occurred
+(chat buffer for incoming messages, roster buffer for presence
+changes, browse buffer for completed queries). They are disabled by
+default. Take care when using them, as they may interrupt your
+editing.
+
+The @code{display} alerts display but do not select the buffer in
+question, using the function @code{display-buffer}. @xref{Choosing
+Window, , Choosing a Window for Display, elisp, GNU Emacs Lisp
+Reference Manual}, for information about customizing its behaviour.
+This is enabled by default for info requests.
+
+The @code{wave} alerts play a sound file by calling
+@code{play-sound-file}. No sound files are provided. To use this,
+enter the names of the sound files in
+@code{jabber-alert-message-wave}, @code{jabber-alert-presence-wave}
+and @code{jabber-alert-info-wave}, respectively.
+
+The @code{screen} alerts send a message through the Screen terminal
+manager (see @uref{http://www.gnu.org/software/screen/}). They do no
+harm if called when you don't use Screen.
+
+The @code{ratpoison} alerts send a message through the Ratpoison
+window manager (see @uref{http://ratpoison.sourceforge.net/}). They
+do no harm if used when you're not running X, but if you are running X
+with another window manager, the ratpoison processes will never exit.
+You can look at them with @code{list-processes}.
+
+The @code{sawfish} alerts send a message through the Sawfish window
+manager.
+
+The @code{festival} alerts speak the message using the Emacs interface
+of the Festival speech synthesis system (see
+@uref{http://www.cstr.ed.ac.uk/projects/festival/}).
+
+Additionally, for one-to-one and MUC messages, there are @code{scroll}
+alerts (enabled by default), that aim to do the right thing with chat
+buffers that are visible but not active. Sometimes you want point to
+scroll down, and sometimes not. These functions should do what you
+mean; if they don't, it's a bug.
+
+Some of these functions are in the @file{jabber-alert.el} file, and the
+others are in their own files. You can use them as templates or
+inspiration for your own alerts.
+
+@node Presence alerts, Message alerts, Standard alerts, Customizing alerts
+@subsection Presence alerts
+
+@vindex jabber-alert-presence-message-function
+@findex jabber-presence-default-message
+
+Set @code{jabber-alert-presence-message-function} to your desired
+function. This function should look like:
+
+@example
+(defun @var{function} (@var{who} @var{oldstatus} @var{newstatus} @var{statustext})
+ ...
+ )
+@end example
+
+@var{who} is the JID symbol (@pxref{Roster structure}),
+@var{oldstatus} and @var{newstatus} are the previous and current
+stati, respectively, and @var{statustext} is the status message if
+provided, otherwise nil.
+
+@var{newstatus} can also be one of @code{"subscribe"},
+@code{"subscribed"}, @code{"unsubscribe"} and @code{"unsubscribed"}.
+
+The default function, @code{jabber-presence-default-message}, returns
+@code{nil} if @var{oldstatus} and @var{newstatus} are the same, and in
+other cases constructs a message from the given data.
+
+All presence alert hooks take the same arguments plus the additional
+@var{proposed-alert}, which is the result of the specified message
+function. This last argument is usually the only one they use.
+
+@node Message alerts, MUC alerts, Presence alerts, Customizing alerts
+@subsection Message alerts
+
+@vindex jabber-alert-message-function
+@findex jabber-message-default-message
+@vindex jabber-message-alert-same-buffer
+
+If you don't want message alerts when the chat buffer in question is
+already the current buffer, set @code{jabber-message-alert-same-buffer}
+to nil. This affects the behaviour of the default message function, so
+you'll have to reimplement this functionality if you write your own
+message function.
+
+Set @code{jabber-alert-message-function} to your desired
+function.@footnote{Logically it should be
+@code{jabber-alert-message-message-function}, but that would be
+really ugly.} This function should look like:
+
+@example
+(defun @var{function} (@var{from} @var{buffer} @var{text})
+ ...
+ )
+@end example
+
+@var{from} is the JID symbol (@pxref{Roster structure}), @var{buffer}
+is the buffer where the message is displayed, and @var{text} is the
+text of the message.
+
+The default function, @code{jabber-message-default-message}, returns
+``Message from @var{person}'', where @var{person} is the name of the
+person if specified in the roster, otherwise the JID.
+
+All message alert hooks take the same arguments plus the additional
+@var{proposed-alert}, which is the result of the specified message
+function.
+
+@node MUC alerts, Info alerts, Message alerts, Customizing alerts
+@subsection MUC alerts
+
+@vindex jabber-alert-muc-function
+@findex jabber-muc-default-message
+
+Set @code{jabber-alert-muc-function} to your desired
+function. This function should look like:
+
+@example
+(defun @var{function} (@var{nick} @var{group} @var{buffer} @var{text})
+ ...
+ )
+@end example
+
+@var{nick} is the nickname, @var{group} is the JID of the group,
+@var{buffer} is the buffer where the message is displayed, and
+@var{text} is the text of the message.
+
+The default function, @code{jabber-muc-default-message}, returns
+``Message from @var{nick} in @var{group}'' or ``Message in
+@var{group}'', the latter for messages from the room itself.
+
+All MUC alert hooks take the same arguments plus the additional
+@var{proposed-alert}, which is the result of the specified message
+function.
+
+@node Info alerts, , MUC alerts, Customizing alerts
+@subsection Info alerts
+
+@vindex jabber-alert-info-message-function
+@findex jabber-info-default-message
+
+Info alerts are sadly underdeveloped. The message function,
+@code{jabber-alert-info-message-function}, takes two arguments,
+@var{infotype} and @var{buffer}. @var{buffer} is the buffer where
+something happened, and @var{infotype} is either @code{'roster} for
+roster updates, or @code{'browse} for anything that uses the browse
+buffer (basically anything except chatting).
+
+The info alert hooks take an extra argument, as could be expected.
+
+@node Hooks, Debug options, Customizing alerts, Customization
+@section Hooks
+
+@vindex jabber-post-connect-hook
+@vindex jabber-pre-disconnect-hook
+@vindex jabber-post-disconnect-hook
+@vindex jabber-lost-connection-hook
+
+jabber.el provides various hooks that you can use for whatever
+purpose.
+
+@table @code
+@item jabber-post-connect-hook
+This hook is called after successful connection and authentication.
+By default it contains @code{jabber-send-default-presence}
+(@pxref{Presence}).
+
+@item jabber-lost-connection-hook
+This hook is called when you have been disconnected for unknown
+reasons. Usually this isn't noticed for quite a long time.
+
+@item jabber-pre-disconnect-hook
+This hook is called just before voluntary disconnection. This might
+be due to failed authentication, so check
+@code{*jabber-authenticated*} if you want to send a stanza.
+
+@item jabber-post-disconnect-hook
+This hook is called after disconnection of any kind, possibly just
+after @code{jabber-lost-connection-hook}.
+
+@end table
+
+@node Debug options, , Hooks, Customization
+@section Debug options
+
+@vindex jabber-debug-log-xml
+
+These settings provide a lot of information which is usually not very
+interesting, but can be useful for debugging various things.
+
+@code{jabber-debug-log-xml} activates XML logging. All XML stanzas
+sent and received are logged in the buffer @code{*-jabber-xml-log-*}
+in list format. @xref{XML representation}.
+
+@node Hacking and extending, Protocol support, Customization, Top
+@chapter Hacking and extending
+
+This part of the manual is an attempt to explain parts of the source
+code. It is not meant to discourage you from reading the code
+yourself and trying to figure it out, but as a guide on where to
+look. Knowledge of Jabber protocols is assumed.
+
+@menu
+* XML representation::
+* Roster structure::
+* Listening for new requests::
+* Sending new requests::
+* Extending service discovery::
+* Chat printers::
+* Stanza chains::
+@end menu
+
+@node XML representation, Roster structure, Hacking and extending, Hacking and extending
+@section XML representation
+
+@cindex XML representation
+
+The XML representation is the one generated by @file{xml.el} in Emacs,
+namely the following. Each tag is a list. The first element of the
+list is a symbol, the name of which is the name of the tag. The
+second element is an alist of attributes, where the keys are the
+attribute names in symbol form, and the values are strings. The
+remaining elements are the tags and data contained within the tag.
+
+For example,
+@example
+<foo bar='baz'>
+<frobozz/>Fnord
+</foo>
+@end example
+is represented as
+@example
+(foo ((bar . "baz")) (frobozz nil "") "Fnord
+")
+@end example
+
+Note the empty string as the third element of the @code{frobozz}
+list. It is not present in newer (post-21.3) versions of
+@file{xml.el}, but it's probably best to assume it might be there.
+
+If you want to see what an XML tag would look like, use
+@code{jabber-sexp2xml}, which takes a tag and returns a string. You
+will usually not need it in your code, as you can use
+@code{jabber-send-sexp} to send away your tags to the server.
+
+@node Roster structure, Listening for new requests, XML representation, Hacking and extending
+@section Roster structure
+
+@vindex *jabber-roster*
+@vindex jabber-jid-obarray
+
+Roster entries are contained in the list @code{*jabber-roster*}.
+
+A roster entry is a symbol. Its name is the JID, and it is interned
+in @code{jabber-jid-obarray}. A roster entry can have the following
+properties:
+
+@table @code
+@item xml
+The XML tag received from the server on roster update
+
+@item name
+The name of the roster item (just like the XML attribute)
+
+@item subscription
+The subscription state (also copied)
+
+@item ask
+The ask state (copied)
+
+@item groups
+A list of strings (possibly empty) containing all the groups the
+contact is in
+
+@item connected
+Boolean, true if any resource is connected
+
+@item show
+Presence show status for highest-priority connected resource
+
+@item status
+Presence status message for highest-priority connected resource
+
+@item resources
+Alist. Keys are strings (resource names), values are plists with
+properties @code{connected}, @code{show}, @code{status} and
+@code{priority}.
+
+@end table
+
+Incoming presence information is inserted in @code{resources}, and the
+information from the resource with the highest priority is inserted in
+@code{show} and @code{status} by the function
+@code{jabber-prioritize-resources}.
+
+@node Listening for new requests, Sending new requests, Roster structure, Hacking and extending
+@section Listening for new requests
+
+@findex jabber-send-iq
+@findex jabber-process-iq
+@findex jabber-signal-error
+@vindex jabber-iq-get-xmlns-alist
+@vindex jabber-iq-set-xmlns-alist
+
+To listen for new IQ requests, add the appropriate entry in
+@code{jabber-iq-get-xmlns-alist} or @code{jabber-iq-set-xmlns-alist}.
+The key is the namespace of the request, and the value is a function
+that takes one argument, the entire IQ stanza in list format.
+@code{jabber-process-iq} reads these alists to determine which
+function to call on incoming packets.
+
+For example, the Ad-Hoc Commands module contains the following:
+
+@example
+(add-to-list 'jabber-iq-set-xmlns-alist
+ (cons "http://jabber.org/protocol/commands" 'jabber-ahc-process))
+@end example
+
+To send a response to an IQ request, use @samp{(jabber-send-iq
+@var{sender} "result" @var{query} nil nil nil nil @var{id})}, where
+@var{query} is the query in list format. @code{jabber-send-iq} will
+encapsulate the query in an IQ packet with the specified id.
+
+To return an error to the Jabber entity that sent the query, use
+@code{jabber-signal-error}. The signal is caught by
+@code{jabber-process-iq}, which takes care of sending the error.
+
+@node Sending new requests, Extending service discovery, Listening for new requests, Hacking and extending
+@section Sending new requests
+
+@findex jabber-send-iq
+@findex jabber-process-iq
+@findex jabber-report-success
+@findex jabber-process-data
+
+To send an IQ request, use @code{jabber-send-iq}. It will generate an
+id, and create a mapping for it for use when the response comes. The
+syntax is:
+
+@example
+(jabber-send-iq @var{to} @var{type} @var{query}
+ @var{success-callback} @var{success-closure}
+ @var{failure-callback} @var{failure-closure})
+@end example
+
+Both callbacks take two arguments, the IQ stanza returned and the
+closure item mentioned here.
+
+Two standard callbacks are provided. @code{jabber-report-success}
+takes a string as closure item, and reports success or failure in the
+echo area. @code{jabber-process-data} prepares a browse buffer. If
+its closure argument is a function, it calls that function with point
+in this browse buffer. If it's a string, it prints that string along
+with the error message in the IQ response. If it's anything else
+(e.g. @code{nil}), it just dumps the XML in the browse buffer.
+
+Examples follow. This is the hypothetical Jabber protocol ``frob'',
+for which only success report is needed:
+@example
+(jabber-send-iq "someone@@somewhere.org" "set"
+ '(query ((xmlns . "frob")))
+ 'jabber-report-success "Frobbing"
+ 'jabber-report-success "Frobbing")
+@end example
+This will print ``Frobbing succeeded'' or ``Frobbing failed: reason'',
+respectively, in the echo area.
+
+The protocol ``investigate'' needs to parse results and show them in a
+browse buffer:
+@example
+(jabber-send-iq "someone@@somewhere.org" "get"
+ '(query ((xmlns . "investigate")))
+ 'jabber-process-data 'jabber-process-investigate
+ 'jabber-process-data "Investigation failed")
+@end example
+Of course, the previous example could have used
+@code{jabber-report-success} for the error message. It's a matter of
+UI taste.
+
+@node Extending service discovery, Chat printers, Sending new requests, Hacking and extending
+@section Service discovery
+
+@vindex jabber-advertised-features
+@vindex jabber-disco-items-nodes
+@vindex jabber-disco-info-nodes
+@findex jabber-my-jid-p
+
+Your new handlers will likely want to advertise their existence
+through service discovery.
+
+To have an additional feature reported in response to disco info
+requests, add a string to @code{jabber-advertised-features}.
+
+By default, the service discovery functions reject all requests
+containing a node identifier with an ``Item not found'' error. To
+make them respond, add the appropriate entries to
+@code{jabber-disco-items-nodes} and @code{jabber-disco-info-nodes}.
+Both variables work in the same way. They are alists, where the keys
+are the node names, and the values are lists of two items.
+
+The first item is the data to return --- either a list or a function
+taking the entire IQ stanza and returning a list, this list containing
+the XML nodes to include in the @code{<query/>} node in the response.
+
+The second item is the access control function. An access control
+function receives the JID as its only argument, and returns non-nil if
+access is to be granted. If nil is specified instead of a function,
+access is always granted. One such function is provided,
+@code{jabber-my-jid-p}, which grants access for JIDs where the
+username and server (not necessarily resource) are equal to those of
+the user.
+
+@node Chat printers, Stanza chains, Extending service discovery, Hacking and extending
+@section Chat printers
+
+@vindex jabber-chat-printers
+@vindex jabber-muc-printers
+@vindex jabber-body-printers
+@cindex Chat printers
+@cindex Body printers
+
+Chat printers are functions that print a certain aspect of an incoming
+message in a chat buffer. Included are functions for printing subjects
+(@code{jabber-chat-print-subject}), bodies
+(@code{jabber-chat-print-body}, and @code{jabber:x:oob}-style URLs
+(@code{jabber-chat-print-url}). The functions in
+@code{jabber-chat-printers} are called in order, with the entire
+@code{<message/>} stanza as argument, and are expected to call
+@code{insert} if they have anything to add.
+
+For MUC, the functions in @code{jabber-muc-printers} are prepended to
+those in @code{jabber-chat-printers}.
+
+Body printers are a subgroup of chat printers. They are exclusive; only
+one of them applies to any given message. The idea is that
+``higher-quality'' parts of the message override pieces included for
+backwards compatibility. Included are @code{jabber-muc-print-invite}
+and @code{jabber-chat-normal-body}; functions for XHTML-IM and PGP
+encrypted messages may be written in the future. The functions in
+@code{jabber-body-printers} are called in order until one of them
+returns non-nil.
+
+@node Stanza chains, , Chat printers, Hacking and extending
+@section Stanza chains
+
+@vindex jabber-message-chain
+@vindex jabber-iq-chain
+@vindex jabber-presence-chain
+
+If you really need to get under the skin of jabber.el, you can add
+functions to the lists @code{jabber-message-chain},
+@code{jabber-iq-chain} and @code{jabber-presence-chain}. The functions
+in these lists will be called in order when an XML stanza of the
+corresponding type arrives, with the entire XML stanza passed as the
+only argument. Earlier functions can modify the stanza to change the
+behaviour of downstream functions.
+
+@node Protocol support, Concept index, Hacking and extending, Top
+@appendix Protocol support
+
+These are the protocols currently supported (in full or partially) by
+jabber.el.
+
+@menu
+* RFC 3920 (XMPP-CORE)::
+* RFC 3921 (XMPP-IM)::
+* JEP-0004 (Data Forms)::
+* JEP-0020 (Feature Negotiation)::
+* JEP-0022 (Message Events)::
+* JEP-0030 (Service Discovery)::
+* JEP-0045 (Multi-User Chat)::
+* JEP-0050 (Ad-Hoc Commands)::
+* JEP-0054 (vcard-temp)::
+* JEP-0055 (Jabber Search)::
+* JEP-0065 (SOCKS5 Bytestreams)::
+* JEP-0066 (Out of Band Data)::
+* JEP-0068 (Field Standardization for Data Forms)::
+* JEP-0077 (In-Band Registration)::
+* JEP-0078 (Non-SASL Authentication)::
+* JEP-0082 (Jabber Date and Time Profiles)::
+* JEP-0086 (Error Condition Mappings)::
+* JEP-0091 (Delayed Delivery)::
+* JEP-0092 (Software Version)::
+* JEP-0095 (Stream Initiation)::
+* JEP-0096 (File Transfer)::
+@end menu
+
+@node RFC 3920 (XMPP-CORE), RFC 3921 (XMPP-IM), Protocol support, Protocol support
+@section RFC 3920 (XMPP-CORE)
+
+Most of RFC 3920 is supported, with the following exceptions.
+
+The STARTTLS feature is not supported.
+
+SASL is supported only when an external SASL library from FLIM or Gnus
+is present. As SASL is an essential part to XMPP, jabber.el will send
+pre-XMPP stream headers if it is not available.
+
+None of the stringprep profiles are implemented. jabber.el changes
+JIDs to lowercase internally; that's all.
+
+jabber.el doesn't interpret namespace prefixes.
+
+The @code{xml:lang} attribute is neither interpreted nor generated.
+
+SRV records are not used.
+
+@node RFC 3921 (XMPP-IM), JEP-0004 (Data Forms), RFC 3920 (XMPP-CORE), Protocol support
+@section RFC 3921 (XMPP-IM)
+
+Most of RFC 3921 is supported, with the following exceptions.
+
+Messages of type ``headline'' are not treated in any special way.
+
+The @code{<thread/>} element is not used or generated.
+
+Neither sending nor receiving ``directed presence'' is supported.
+
+Privacy lists are not supported at all.
+
+jabber.el doesn't support XMPP-E2E or ``im:'' CPIM URIs.
+
+@node JEP-0004 (Data Forms), JEP-0020 (Feature Negotiation), RFC 3921 (XMPP-IM), Protocol support
+@section JEP-0004 (Data Forms)
+
+JEP-0004 support is good enough for many purposes. Limitations are
+the following.
+
+Forms in incoming messages are not interpreted. See each specific
+protocol for whether forms are accepted in that context.
+
+``Cancel'' messages are probably not consistently generated when they
+should be. This is partly a paradigm clash, as jabber.el doesn't use
+modal dialog boxes but buffers which can easily be buried.
+
+@code{<required/>} elements are not enforced.
+
+The field types ``jid-single'', ``jid-multi'' and ``list-multi'' are
+not implemented, due to programmer laziness. Let us know if you need
+them.
+
+@node JEP-0020 (Feature Negotiation), JEP-0022 (Message Events), JEP-0004 (Data Forms), Protocol support
+@section JEP-0020 (Feature Negotiation)
+
+There are no known limitations or bugs in JEP-0020 support.
+
+@node JEP-0022 (Message Events), JEP-0030 (Service Discovery), JEP-0020 (Feature Negotiation), Protocol support
+@section JEP-0022 (Message Events)
+
+jabber.el understands all four specified kinds of message events
+(offline, delivered, displayed, and composing) and by default requests
+all of them. It also reports those three events that make sense for
+clients.
+
+@node JEP-0030 (Service Discovery), JEP-0045 (Multi-User Chat), JEP-0022 (Message Events), Protocol support
+@section JEP-0030 (Service Discovery)
+
+Service discovery is supported, both as client and server, with the
+following limitations. Currently, the client part is not used by any
+code, but is provided only as a user tool. The user interface is not
+appealing.
+
+Publishing items is not supported.
+
+@node JEP-0045 (Multi-User Chat), JEP-0050 (Ad-Hoc Commands), JEP-0030 (Service Discovery), Protocol support
+@section JEP-0045 (Multi-User Chat)
+
+jabber.el supports parts of JEP-0045. Entering, leaving and chatting
+work. So do invitations and private messages. Room configuration is
+supported. Changing roles of participants (basic moderation) is
+implemented, as is changing affiliations, but requesting affiliation
+lists is not yet supported.
+
+@node JEP-0050 (Ad-Hoc Commands), JEP-0054 (vcard-temp), JEP-0045 (Multi-User Chat), Protocol support
+@section JEP-0050 (Ad-Hoc Commands)
+
+jabber.el is probably the first implementation of JEP-0050 (see
+@uref{http://article.gmane.org/gmane.network.jabber.devel/21413, post
+on jdev from 2004-03-10}). Both the client and server parts are
+supported.
+
+@node JEP-0054 (vcard-temp), JEP-0055 (Jabber Search), JEP-0050 (Ad-Hoc Commands), Protocol support
+@section JEP-0054 (vcard-temp)
+
+Both displaying other users' vCards and editing your own vCard are
+supported. The implementation tries to follow the schema in the JEP
+accurately.
+
+@node JEP-0055 (Jabber Search), JEP-0065 (SOCKS5 Bytestreams), JEP-0054 (vcard-temp), Protocol support
+@section JEP-0055 (Jabber Search)
+
+JEP-0055 is supported, both with traditional fields and with Data
+Forms (@pxref{JEP-0004 (Data Forms)}). As the traditional fields
+specified by the JEP is a subset of those allowed in JEP-0077,
+handling of those two form types are merged. @ref{JEP-0077 (In-Band
+Registration)}.
+
+@node JEP-0065 (SOCKS5 Bytestreams), JEP-0066 (Out of Band Data), JEP-0055 (Jabber Search), Protocol support
+@section JEP-0065 (SOCKS5 Bytestreams)
+
+JEP-0065 support seems to work, but it is not yet enabled by default.
+
+Currently jabber.el cannot act as a server, not even on on Emacsen
+that support server sockets (GNU Emacs 22 and up). Therefore it
+relies on proxies. Proxies have to be entered and queried manually.
+
+Psi's ``fast mode''
+(@uref{http://delta.affinix.com/specs/stream.html}), which gives
+greater flexibility with regards to NAT, is not implemented.
+
+@node JEP-0066 (Out of Band Data), JEP-0068 (Field Standardization for Data Forms), JEP-0065 (SOCKS5 Bytestreams), Protocol support
+@section JEP-0066 (Out of Band Data)
+
+jabber.el will display URLs sent in message stanzas qualified by
+the @code{jabber:x:oob} namespace, as described in this JEP. Sending
+such URLs or doing anything with iq stanzas (using the
+@code{jabber:iq:oob} namespace) is not supported.
+
+@node JEP-0068 (Field Standardization for Data Forms), JEP-0077 (In-Band Registration), JEP-0066 (Out of Band Data), Protocol support
+@section JEP-0068 (Field Standardization for Data Forms)
+
+JEP-0068 is only used in the context of creating a new Jabber account,
+to prefill the username field of the registration form.
+
+@node JEP-0077 (In-Band Registration), JEP-0078 (Non-SASL Authentication), JEP-0068 (Field Standardization for Data Forms), Protocol support
+@section JEP-0077 (In-Band Registration)
+
+In-band registration is supported for all purposes. That means
+registering a new Jabber account, changing Jabber password, removing a
+Jabber account, registering with a service, and cancelling
+registration to a service. Data forms are supported as well. URL
+redirections are not.
+
+jabber.el will not prevent or alert a user trying to change a password
+over an unencrypted connection.
+
+@node JEP-0078 (Non-SASL Authentication), JEP-0082 (Jabber Date and Time Profiles), JEP-0077 (In-Band Registration), Protocol support
+@section JEP-0078 (Non-SASL Authentication)
+
+Non-SASL authentication is supported, both plaintext and digest.
+Digest is preferred, and a warning is displayed to the user if only
+plaintext is available.
+
+@node JEP-0082 (Jabber Date and Time Profiles), JEP-0086 (Error Condition Mappings), JEP-0078 (Non-SASL Authentication), Protocol support
+@section JEP-0082 (Jabber Date and Time Profiles)
+
+The DateTime profile of JEP-0082 is supported. Currently this is only
+used for file transfer.
+
+@node JEP-0086 (Error Condition Mappings), JEP-0091 (Delayed Delivery), JEP-0082 (Jabber Date and Time Profiles), Protocol support
+@section JEP-0086 (Error Condition Mappings)
+
+Legacy errors are interpreted, but never generated. XMPP style error
+messages take precedence when errors are reported to the user.
+
+@node JEP-0091 (Delayed Delivery), JEP-0092 (Software Version), JEP-0086 (Error Condition Mappings), Protocol support
+@section JEP-0091 (Delayed Delivery)
+
+The time specified on delayed incoming messages is interpreted, and
+displayed in chat buffers instead of the current time.
+
+@node JEP-0092 (Software Version), JEP-0095 (Stream Initiation), JEP-0091 (Delayed Delivery), Protocol support
+@section JEP-0092 (Software Version)
+
+The user can request the version of any entity. jabber.el answers
+version requests to anyone, giving ``jabber.el'' as name, and the
+Emacs version as OS.
+
+@node JEP-0095 (Stream Initiation), JEP-0096 (File Transfer), JEP-0092 (Software Version), Protocol support
+@section JEP-0095 (Stream Initiation)
+
+JEP-0095 is supported, both incoming and outgoing, with the following
+exceptions. It is not enabled by default, as it hasn't received much
+testing.
+
+jabber.el doesn't check service discovery results before sending a
+stream initiation request.
+
+@node JEP-0096 (File Transfer), , JEP-0095 (Stream Initiation), Protocol support
+@section JEP-0096 (File Transfer)
+
+Both sending and receiving files is supported, though not enabled by
+default. The following limitations apply.
+
+The hash of a file being sent is not calculated and sent in the
+request.
+
+Ranged transfers are not supported.
+
+In-band bytestreams are not yet supported, even though JEP-0096
+requires them.
+
+@node Concept index, Function index, Protocol support, Top
+@unnumbered Concept index
+
+@printindex cp
+
+@node Function index, Variable index, Concept index, Top
+@unnumbered Function index
+
+@printindex fn
+
+@node Variable index, , Function index, Top
+@unnumbered Variable index
+
+@printindex vr
+
+@bye
+
+@ignore
+ arch-tag: 995bf3da-0e87-4b15-895a-1e85fac139a2
+@end ignore
diff --git a/sha1.el b/sha1.el
new file mode 100644
index 0000000..99b9aa4
--- /dev/null
+++ b/sha1.el
@@ -0,0 +1,443 @@
+;;; sha1.el --- SHA1 Secure Hash Algorithm in Emacs-Lisp
+
+;; Copyright (C) 1999, 2001, 2002, 2003, 2004,
+;; 2005 Free Software Foundation, Inc.
+
+;; Author: Shuhei KOBAYASHI <shuhei@aqua.ocn.ne.jp>
+;; Keywords: SHA1, FIPS 180-1
+
+;; This file is part of FLIM (Faithful Library about Internet Message).
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; This program is implemented from the definition of SHA-1 in FIPS PUB
+;; 180-1 (Federal Information Processing Standards Publication 180-1),
+;; "Announcing the Standard for SECURE HASH STANDARD".
+;; <URL:http://www.itl.nist.gov/div897/pubs/fip180-1.htm>
+;; (EXCEPTION; two optimizations taken from GnuPG/cipher/sha1.c)
+;;
+;; Test cases from FIPS PUB 180-1.
+;;
+;; (sha1 "abc")
+;; => a9993e364706816aba3e25717850c26c9cd0d89d
+;;
+;; (sha1 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
+;; => 84983e441c3bd26ebaae4aa1f95129e5e54670f1
+;;
+;; (sha1 (make-string 1000000 ?a))
+;; => 34aa973cd4c4daa4f61eeb2bdbad27316534016f
+;;
+;; BUGS:
+;; * It is assumed that length of input string is less than 2^29 bytes.
+;; * It is caller's responsibility to make string (or region) unibyte.
+;;
+;; TODO:
+;; * Rewrite from scratch!
+;; This version is much faster than Keiichi Suzuki's another sha1.el,
+;; but it is too dirty.
+
+;;; Code:
+
+(require 'hex-util)
+
+;;;
+;;; external SHA1 function.
+;;;
+
+(defgroup sha1 nil
+ "Elisp interface for SHA1 hash computation."
+ :version "22.1"
+ :group 'extensions)
+
+(defcustom sha1-maximum-internal-length 500
+ "*Maximum length of message to use Lisp version of SHA1 function.
+If message is longer than this, `sha1-program' is used instead.
+
+If this variable is set to 0, use external program only.
+If this variable is set to nil, use internal function only."
+ :type 'integer
+ :group 'sha1)
+
+(defcustom sha1-program '("sha1sum")
+ "*Name of program to compute SHA1.
+It must be a string \(program name\) or list of strings \(name and its args\)."
+ :type '(repeat string)
+ :group 'sha1)
+
+(defcustom sha1-use-external (condition-case ()
+ (executable-find (car sha1-program))
+ (error))
+ "*Use external SHA1 program.
+If this variable is set to nil, use internal function only."
+ :type 'boolean
+ :group 'sha1)
+
+(defun sha1-string-external (string &optional binary)
+ (let (prog args digest default-enable-multibyte-characters)
+ (if (consp sha1-program)
+ (setq prog (car sha1-program)
+ args (cdr sha1-program))
+ (setq prog sha1-program
+ args nil))
+ (with-temp-buffer
+ (insert string)
+ (apply (function call-process-region)
+ (point-min)(point-max)
+ prog t t nil args)
+ ;; SHA1 is 40 bytes long in hexadecimal form.
+ (setq digest (buffer-substring (point-min)(+ (point-min) 40))))
+ (if binary
+ (decode-hex-string digest)
+ digest)))
+
+(defun sha1-region-external (beg end &optional binary)
+ (sha1-string-external (buffer-substring-no-properties beg end) binary))
+
+;;;
+;;; internal SHA1 function.
+;;;
+
+(eval-when-compile
+ ;; optional second arg of string-to-number is new in v20.
+ (defconst sha1-K0-high 23170) ; (string-to-number "5A82" 16)
+ (defconst sha1-K0-low 31129) ; (string-to-number "7999" 16)
+ (defconst sha1-K1-high 28377) ; (string-to-number "6ED9" 16)
+ (defconst sha1-K1-low 60321) ; (string-to-number "EBA1" 16)
+ (defconst sha1-K2-high 36635) ; (string-to-number "8F1B" 16)
+ (defconst sha1-K2-low 48348) ; (string-to-number "BCDC" 16)
+ (defconst sha1-K3-high 51810) ; (string-to-number "CA62" 16)
+ (defconst sha1-K3-low 49622) ; (string-to-number "C1D6" 16)
+
+;;; original definition of sha1-F0.
+;;; (defmacro sha1-F0 (B C D)
+;;; (` (logior (logand (, B) (, C))
+;;; (logand (lognot (, B)) (, D)))))
+;;; a little optimization from GnuPG/cipher/sha1.c.
+ (defmacro sha1-F0 (B C D)
+ (` (logxor (, D) (logand (, B) (logxor (, C) (, D))))))
+ (defmacro sha1-F1 (B C D)
+ (` (logxor (, B) (, C) (, D))))
+;;; original definition of sha1-F2.
+;;; (defmacro sha1-F2 (B C D)
+;;; (` (logior (logand (, B) (, C))
+;;; (logand (, B) (, D))
+;;; (logand (, C) (, D)))))
+;;; a little optimization from GnuPG/cipher/sha1.c.
+ (defmacro sha1-F2 (B C D)
+ (` (logior (logand (, B) (, C))
+ (logand (, D) (logior (, B) (, C))))))
+ (defmacro sha1-F3 (B C D)
+ (` (logxor (, B) (, C) (, D))))
+
+ (defmacro sha1-S1 (W-high W-low)
+ (` (let ((W-high (, W-high))
+ (W-low (, W-low)))
+ (setq S1W-high (+ (% (* W-high 2) 65536)
+ (/ W-low (, (/ 65536 2)))))
+ (setq S1W-low (+ (/ W-high (, (/ 65536 2)))
+ (% (* W-low 2) 65536))))))
+ (defmacro sha1-S5 (A-high A-low)
+ (` (progn
+ (setq S5A-high (+ (% (* (, A-high) 32) 65536)
+ (/ (, A-low) (, (/ 65536 32)))))
+ (setq S5A-low (+ (/ (, A-high) (, (/ 65536 32)))
+ (% (* (, A-low) 32) 65536))))))
+ (defmacro sha1-S30 (B-high B-low)
+ (` (progn
+ (setq S30B-high (+ (/ (, B-high) 4)
+ (* (% (, B-low) 4) (, (/ 65536 4)))))
+ (setq S30B-low (+ (/ (, B-low) 4)
+ (* (% (, B-high) 4) (, (/ 65536 4))))))))
+
+ (defmacro sha1-OP (round)
+ (` (progn
+ (sha1-S5 sha1-A-high sha1-A-low)
+ (sha1-S30 sha1-B-high sha1-B-low)
+ (setq sha1-A-low (+ ((, (intern (format "sha1-F%d" round)))
+ sha1-B-low sha1-C-low sha1-D-low)
+ sha1-E-low
+ (, (symbol-value
+ (intern (format "sha1-K%d-low" round))))
+ (aref block-low idx)
+ (progn
+ (setq sha1-E-low sha1-D-low)
+ (setq sha1-D-low sha1-C-low)
+ (setq sha1-C-low S30B-low)
+ (setq sha1-B-low sha1-A-low)
+ S5A-low)))
+ (setq carry (/ sha1-A-low 65536))
+ (setq sha1-A-low (% sha1-A-low 65536))
+ (setq sha1-A-high (% (+ ((, (intern (format "sha1-F%d" round)))
+ sha1-B-high sha1-C-high sha1-D-high)
+ sha1-E-high
+ (, (symbol-value
+ (intern (format "sha1-K%d-high" round))))
+ (aref block-high idx)
+ (progn
+ (setq sha1-E-high sha1-D-high)
+ (setq sha1-D-high sha1-C-high)
+ (setq sha1-C-high S30B-high)
+ (setq sha1-B-high sha1-A-high)
+ S5A-high)
+ carry)
+ 65536)))))
+
+ (defmacro sha1-add-to-H (H X)
+ (` (progn
+ (setq (, (intern (format "sha1-%s-low" H)))
+ (+ (, (intern (format "sha1-%s-low" H)))
+ (, (intern (format "sha1-%s-low" X)))))
+ (setq carry (/ (, (intern (format "sha1-%s-low" H))) 65536))
+ (setq (, (intern (format "sha1-%s-low" H)))
+ (% (, (intern (format "sha1-%s-low" H))) 65536))
+ (setq (, (intern (format "sha1-%s-high" H)))
+ (% (+ (, (intern (format "sha1-%s-high" H)))
+ (, (intern (format "sha1-%s-high" X)))
+ carry)
+ 65536)))))
+ )
+
+;;; buffers (H0 H1 H2 H3 H4).
+(defvar sha1-H0-high)
+(defvar sha1-H0-low)
+(defvar sha1-H1-high)
+(defvar sha1-H1-low)
+(defvar sha1-H2-high)
+(defvar sha1-H2-low)
+(defvar sha1-H3-high)
+(defvar sha1-H3-low)
+(defvar sha1-H4-high)
+(defvar sha1-H4-low)
+
+(defun sha1-block (block-high block-low)
+ (let (;; step (c) --- initialize buffers (A B C D E).
+ (sha1-A-high sha1-H0-high) (sha1-A-low sha1-H0-low)
+ (sha1-B-high sha1-H1-high) (sha1-B-low sha1-H1-low)
+ (sha1-C-high sha1-H2-high) (sha1-C-low sha1-H2-low)
+ (sha1-D-high sha1-H3-high) (sha1-D-low sha1-H3-low)
+ (sha1-E-high sha1-H4-high) (sha1-E-low sha1-H4-low)
+ (idx 16))
+ ;; step (b).
+ (let (;; temporary variables used in sha1-S1 macro.
+ S1W-high S1W-low)
+ (while (< idx 80)
+ (sha1-S1 (logxor (aref block-high (- idx 3))
+ (aref block-high (- idx 8))
+ (aref block-high (- idx 14))
+ (aref block-high (- idx 16)))
+ (logxor (aref block-low (- idx 3))
+ (aref block-low (- idx 8))
+ (aref block-low (- idx 14))
+ (aref block-low (- idx 16))))
+ (aset block-high idx S1W-high)
+ (aset block-low idx S1W-low)
+ (setq idx (1+ idx))))
+ ;; step (d).
+ (setq idx 0)
+ (let (;; temporary variables used in sha1-OP macro.
+ S5A-high S5A-low S30B-high S30B-low carry)
+ (while (< idx 20) (sha1-OP 0) (setq idx (1+ idx)))
+ (while (< idx 40) (sha1-OP 1) (setq idx (1+ idx)))
+ (while (< idx 60) (sha1-OP 2) (setq idx (1+ idx)))
+ (while (< idx 80) (sha1-OP 3) (setq idx (1+ idx))))
+ ;; step (e).
+ (let (;; temporary variables used in sha1-add-to-H macro.
+ carry)
+ (sha1-add-to-H H0 A)
+ (sha1-add-to-H H1 B)
+ (sha1-add-to-H H2 C)
+ (sha1-add-to-H H3 D)
+ (sha1-add-to-H H4 E))))
+
+(defun sha1-binary (string)
+ "Return the SHA1 of STRING in binary form."
+ (let (;; prepare buffers for a block. byte-length of block is 64.
+ ;; input block is split into two vectors.
+ ;;
+ ;; input block: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...
+ ;; block-high: +-0-+ +-1-+ +-2-+ +-3-+
+ ;; block-low: +-0-+ +-1-+ +-2-+ +-3-+
+ ;;
+ ;; length of each vector is 80, and elements of each vector are
+ ;; 16bit integers. elements 0x10-0x4F of each vector are
+ ;; assigned later in `sha1-block'.
+ (block-high (eval-when-compile (make-vector 80 nil)))
+ (block-low (eval-when-compile (make-vector 80 nil))))
+ (unwind-protect
+ (let* (;; byte-length of input string.
+ (len (length string))
+ (lim (* (/ len 64) 64))
+ (rem (% len 4))
+ (idx 0)(pos 0))
+ ;; initialize buffers (H0 H1 H2 H3 H4).
+ (setq sha1-H0-high 26437 ; (string-to-number "6745" 16)
+ sha1-H0-low 8961 ; (string-to-number "2301" 16)
+ sha1-H1-high 61389 ; (string-to-number "EFCD" 16)
+ sha1-H1-low 43913 ; (string-to-number "AB89" 16)
+ sha1-H2-high 39098 ; (string-to-number "98BA" 16)
+ sha1-H2-low 56574 ; (string-to-number "DCFE" 16)
+ sha1-H3-high 4146 ; (string-to-number "1032" 16)
+ sha1-H3-low 21622 ; (string-to-number "5476" 16)
+ sha1-H4-high 50130 ; (string-to-number "C3D2" 16)
+ sha1-H4-low 57840) ; (string-to-number "E1F0" 16)
+ ;; loop for each 64 bytes block.
+ (while (< pos lim)
+ ;; step (a).
+ (setq idx 0)
+ (while (< idx 16)
+ (aset block-high idx (+ (* (aref string pos) 256)
+ (aref string (1+ pos))))
+ (setq pos (+ pos 2))
+ (aset block-low idx (+ (* (aref string pos) 256)
+ (aref string (1+ pos))))
+ (setq pos (+ pos 2))
+ (setq idx (1+ idx)))
+ (sha1-block block-high block-low))
+ ;; last block.
+ (if (prog1
+ (< (- len lim) 56)
+ (setq lim (- len rem))
+ (setq idx 0)
+ (while (< pos lim)
+ (aset block-high idx (+ (* (aref string pos) 256)
+ (aref string (1+ pos))))
+ (setq pos (+ pos 2))
+ (aset block-low idx (+ (* (aref string pos) 256)
+ (aref string (1+ pos))))
+ (setq pos (+ pos 2))
+ (setq idx (1+ idx)))
+ ;; this is the last (at most) 32bit word.
+ (cond
+ ((= rem 3)
+ (aset block-high idx (+ (* (aref string pos) 256)
+ (aref string (1+ pos))))
+ (setq pos (+ pos 2))
+ (aset block-low idx (+ (* (aref string pos) 256)
+ 128)))
+ ((= rem 2)
+ (aset block-high idx (+ (* (aref string pos) 256)
+ (aref string (1+ pos))))
+ (aset block-low idx 32768))
+ ((= rem 1)
+ (aset block-high idx (+ (* (aref string pos) 256)
+ 128))
+ (aset block-low idx 0))
+ (t ;; (= rem 0)
+ (aset block-high idx 32768)
+ (aset block-low idx 0)))
+ (setq idx (1+ idx))
+ (while (< idx 16)
+ (aset block-high idx 0)
+ (aset block-low idx 0)
+ (setq idx (1+ idx))))
+ ;; last block has enough room to write the length of string.
+ (progn
+ ;; write bit length of string to last 4 bytes of the block.
+ (aset block-low 15 (* (% len 8192) 8))
+ (setq len (/ len 8192))
+ (aset block-high 15 (% len 65536))
+ ;; XXX: It is not practical to compute SHA1 of
+ ;; such a huge message on emacs.
+ ;; (setq len (/ len 65536)) ; for 64bit emacs.
+ ;; (aset block-low 14 (% len 65536))
+ ;; (aset block-high 14 (/ len 65536))
+ (sha1-block block-high block-low))
+ ;; need one more block.
+ (sha1-block block-high block-low)
+ (fillarray block-high 0)
+ (fillarray block-low 0)
+ ;; write bit length of string to last 4 bytes of the block.
+ (aset block-low 15 (* (% len 8192) 8))
+ (setq len (/ len 8192))
+ (aset block-high 15 (% len 65536))
+ ;; XXX: It is not practical to compute SHA1 of
+ ;; such a huge message on emacs.
+ ;; (setq len (/ len 65536)) ; for 64bit emacs.
+ ;; (aset block-low 14 (% len 65536))
+ ;; (aset block-high 14 (/ len 65536))
+ (sha1-block block-high block-low))
+ ;; make output string (in binary form).
+ (let ((result (make-string 20 0)))
+ (aset result 0 (/ sha1-H0-high 256))
+ (aset result 1 (% sha1-H0-high 256))
+ (aset result 2 (/ sha1-H0-low 256))
+ (aset result 3 (% sha1-H0-low 256))
+ (aset result 4 (/ sha1-H1-high 256))
+ (aset result 5 (% sha1-H1-high 256))
+ (aset result 6 (/ sha1-H1-low 256))
+ (aset result 7 (% sha1-H1-low 256))
+ (aset result 8 (/ sha1-H2-high 256))
+ (aset result 9 (% sha1-H2-high 256))
+ (aset result 10 (/ sha1-H2-low 256))
+ (aset result 11 (% sha1-H2-low 256))
+ (aset result 12 (/ sha1-H3-high 256))
+ (aset result 13 (% sha1-H3-high 256))
+ (aset result 14 (/ sha1-H3-low 256))
+ (aset result 15 (% sha1-H3-low 256))
+ (aset result 16 (/ sha1-H4-high 256))
+ (aset result 17 (% sha1-H4-high 256))
+ (aset result 18 (/ sha1-H4-low 256))
+ (aset result 19 (% sha1-H4-low 256))
+ result))
+ ;; do not leave a copy of input string.
+ (fillarray block-high nil)
+ (fillarray block-low nil))))
+
+(defun sha1-string-internal (string &optional binary)
+ (if binary
+ (sha1-binary string)
+ (encode-hex-string (sha1-binary string))))
+
+(defun sha1-region-internal (beg end &optional binary)
+ (sha1-string-internal (buffer-substring-no-properties beg end) binary))
+
+;;;
+;;; application interface.
+;;;
+
+(defun sha1-region (beg end &optional binary)
+ (if (and sha1-use-external
+ sha1-maximum-internal-length
+ (> (abs (- end beg)) sha1-maximum-internal-length))
+ (sha1-region-external beg end binary)
+ (sha1-region-internal beg end binary)))
+
+(defun sha1-string (string &optional binary)
+ (if (and sha1-use-external
+ sha1-maximum-internal-length
+ (> (length string) sha1-maximum-internal-length))
+ (sha1-string-external string binary)
+ (sha1-string-internal string binary)))
+
+;;;###autoload
+(defun sha1 (object &optional beg end binary)
+ "Return the SHA1 (Secure Hash Algorithm) of an object.
+OBJECT is either a string or a buffer.
+Optional arguments BEG and END denote buffer positions for computing the
+hash of a portion of OBJECT.
+If BINARY is non-nil, return a string in binary form."
+ (if (stringp object)
+ (sha1-string object binary)
+ (save-excursion
+ (set-buffer object)
+ (sha1-region (or beg (point-min)) (or end (point-max)) binary))))
+
+(provide 'sha1)
+
+;;; arch-tag: c0f9abd0-ffc1-4557-aac6-ece7f2d4c901
+;;; sha1.el ends here