1 ;; jabber-disco.el - service discovery functions -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2003, 2004, 2007, 2008 - Magnus Henoch - mange@freemail.hu
4 ;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
6 ;; This file is a part of jabber.el.
8 ;; This program is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2 of the License, or
11 ;; (at your option) any later version.
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program; if not, write to the Free Software
20 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 (require 'jabber-menu)
26 ;;; Respond to disco requests
29 (eval-after-load "jabber-core"
30 '(add-to-list 'jabber-presence-chain #'jabber-process-caps))
32 (defvar jabber-caps-cache (make-hash-table :test 'equal))
34 (defconst jabber-caps-hash-names
35 (if (fboundp 'secure-hash)
41 ;; `secure-hash' was introduced in Emacs 24. For Emacs 23, fall
42 ;; back to the `sha1' function, handled specially in
43 ;; `jabber-caps--secure-hash'.
45 "Hash function name map.
46 Maps names defined in http://www.iana.org/assignments/hash-function-text-names
47 to symbols accepted by `secure-hash'.
49 XEP-0115 currently recommends SHA-1, but let's be future-proof.")
51 (defun jabber-caps-get-cached (jid)
52 "Get disco info from Entity Capabilities cache.
53 JID should be a string containing a full JID.
54 Return (IDENTITIES FEATURES), or nil if not in cache."
55 (let* ((symbol (jabber-jid-symbol jid))
56 (resource (or (jabber-jid-resource jid) ""))
57 (resource-plist (cdr (assoc resource (get symbol 'resources))))
58 (key (plist-get resource-plist 'caps)))
60 (let ((cache-entry (gethash key jabber-caps-cache)))
61 (when (and (consp cache-entry) (not (floatp (car cache-entry))))
65 (defun jabber-process-caps (jc xml-data)
66 "Look for entity capabilities in presence stanzas.
68 JC is the Jabber connection.
69 XML-DATA is the parsed tree data from the stream (stanzas)
70 obtained from `xml-parse-region'."
71 (let* ((from (jabber-xml-get-attribute xml-data 'from))
72 (type (jabber-xml-get-attribute xml-data 'type))
73 (c (jabber-xml-path xml-data '(("http://jabber.org/protocol/caps" . "c")))))
74 (when (and (null type) c)
75 (jabber-xml-let-attributes
79 ;; If the <c/> element has a hash attribute, it follows the
80 ;; "modern" version of XEP-0115.
81 (jabber-process-caps-modern jc from hash node ver))
83 ;; No hash attribute. Use legacy version of XEP-0115.
84 ;; TODO: do something clever here.
87 (defun jabber-process-caps-modern (jc jid hash node ver)
88 (when (assoc hash jabber-caps-hash-names)
89 ;; We support the hash function used.
90 (let* ((key (cons hash ver))
91 (cache-entry (gethash key jabber-caps-cache)))
92 ;; Remember the hash in the JID symbol.
93 (let* ((symbol (jabber-jid-symbol jid))
94 (resource (or (jabber-jid-resource jid) ""))
95 (resource-entry (assoc resource (get symbol 'resources)))
96 (new-resource-plist (plist-put (cdr resource-entry) 'caps key)))
98 (setf (cdr resource-entry) new-resource-plist)
99 (push (cons resource new-resource-plist) (get symbol 'resources))))
101 (cl-flet ((request-disco-info
106 `(query ((xmlns . "http://jabber.org/protocol/disco#info")
107 (node . ,(concat node "#" ver))))
108 #'jabber-process-caps-info-result (list hash node ver)
109 #'jabber-process-caps-info-error (list hash node ver))))
111 ((and (consp cache-entry)
112 (floatp (car cache-entry)))
113 ;; We have a record of asking someone about this hash.
114 (if (< (- (float-time) (car cache-entry)) 10.0)
115 ;; We asked someone about this hash less than 10 seconds ago.
116 ;; Let's add the new JID to the entry, just in case that
118 (cl-pushnew jid (cdr cache-entry) :test #'string=)
119 ;; We asked someone about it more than 10 seconds ago.
120 ;; They're probably not going to answer. Let's ask
121 ;; this contact about it instead.
122 (setf (car cache-entry) (float-time))
123 (request-disco-info)))
125 ;; We know nothing about this hash. Let's note the
126 ;; fact that we tried to get information about it.
127 (puthash key (list (float-time)) jabber-caps-cache)
128 (request-disco-info))
130 ;; We already know what this hash represents, so we
131 ;; can cache info for this contact.
132 (puthash (cons jid nil) cache-entry jabber-disco-info-cache)))))))
134 (defun jabber-process-caps-info-result (jc xml-data closure-data)
135 (cl-destructuring-bind (hash node ver) closure-data
136 (let* ((key (cons hash ver))
137 (query (jabber-iq-query xml-data))
138 (verification-string (jabber-caps-ver-string query hash)))
139 (if (string= ver verification-string)
140 ;; The hash is correct; save info.
141 (puthash key (jabber-disco-parse-info xml-data) jabber-caps-cache)
142 ;; The hash is incorrect.
143 (jabber-caps-try-next jc hash node ver)))))
145 (defun jabber-process-caps-info-error (jc xml-data closure-data)
146 (cl-destructuring-bind (hash node ver) closure-data
147 (jabber-caps-try-next jc hash node ver)))
149 (defun jabber-caps-try-next (jc hash node ver)
150 (let* ((key (cons hash ver))
151 (cache-entry (gethash key jabber-caps-cache)))
152 (when (floatp (car-safe cache-entry))
153 (let ((next-jid (pop (cdr cache-entry))))
154 ;; Do we know someone else we could ask about this hash?
157 (setf (car cache-entry) (float-time))
161 `(query ((xmlns . "http://jabber.org/protocol/disco#info")
162 (node . ,(concat node "#" ver))))
163 #'jabber-process-caps-info-result (list hash node ver)
164 #'jabber-process-caps-info-error (list hash node ver)))
165 ;; No, forget about it for now.
166 (remhash key jabber-caps-cache))))))
168 (defun jabber-caps-ver-string (query hash)
169 ;; XEP-0115, section 5.1
170 ;; 1. Initialize an empty string S.
172 (let* ((identities (jabber-xml-get-children query 'identity))
173 (disco-features (mapcar (lambda (f) (jabber-xml-get-attribute f 'var))
174 (jabber-xml-get-children query 'feature)))
175 (maybe-forms (jabber-xml-get-children query 'x))
176 (forms (cl-remove-if-not
178 ;; Keep elements that are forms and have a FORM_TYPE,
179 ;; according to XEP-0128.
180 (and (string= (jabber-xml-get-xmlns x) "jabber:x:data")
181 (jabber-xdata-formtype x)))
183 ;; 2. Sort the service discovery identities [15] by category
184 ;; and then by type and then by xml:lang (if it exists),
185 ;; formatted as CATEGORY '/' [TYPE] '/' [LANG] '/'
186 ;; [NAME]. [16] Note that each slash is included even if the
187 ;; LANG or NAME is not included (in accordance with XEP-0030,
188 ;; the category and type MUST be included.
189 (setq identities (sort identities #'jabber-caps-identity-<))
190 ;; 3. For each identity, append the 'category/type/lang/name' to
191 ;; S, followed by the '<' character.
192 (dolist (identity identities)
193 (jabber-xml-let-attributes (category type xml:lang name) identity
194 ;; Use `concat' here instead of passing everything to
195 ;; `insert', since `concat' tolerates nil values.
196 (insert (concat category "/" type "/" xml:lang "/" name "<"))))
197 ;; 4. Sort the supported service discovery features. [17]
198 (setq disco-features (sort disco-features #'string<))
199 ;; 5. For each feature, append the feature to S, followed by the
201 (dolist (f disco-features)
203 ;; 6. If the service discovery information response includes
204 ;; XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e.,
205 ;; by the XML character data of the <value/> element).
206 (setq forms (sort forms (lambda (a b)
207 (string< (jabber-xdata-formtype a)
208 (jabber-xdata-formtype b)))))
209 ;; 7. For each extended service discovery information form:
211 ;; Append the XML character data of the FORM_TYPE field's
212 ;; <value/> element, followed by the '<' character.
213 (insert (jabber-xdata-formtype form) "<")
214 ;; Sort the fields by the value of the "var" attribute.
215 (let ((fields (sort (jabber-xml-get-children form 'field)
217 (string< (jabber-xml-get-attribute a 'var)
218 (jabber-xml-get-attribute b 'var))))))
219 (dolist (field fields)
220 ;; For each field other than FORM_TYPE:
221 (unless (string= (jabber-xml-get-attribute field 'var) "FORM_TYPE")
222 ;; Append the value of the "var" attribute, followed by the '<' character.
223 (insert (jabber-xml-get-attribute field 'var) "<")
224 ;; Sort values by the XML character data of the <value/> element.
225 (let ((values (sort (mapcar (lambda (value)
226 (car (jabber-xml-node-children value)))
227 (jabber-xml-get-children field 'value))
229 ;; For each <value/> element, append the XML character
230 ;; data, followed by the '<' character.
231 (dolist (value values)
232 (insert value "<"))))))))
234 ;; 8. Ensure that S is encoded according to the UTF-8 encoding
236 (let ((s (encode-coding-string (buffer-string) 'utf-8 t))
237 (algorithm (cdr (assoc hash jabber-caps-hash-names))))
238 ;; 9. Compute the verification string by hashing S using the
239 ;; algorithm specified in the 'hash' attribute (e.g., SHA-1 as
240 ;; defined in RFC 3174 [19]). The hashed data MUST be generated
241 ;; with binary output and encoded using Base64 as specified in
242 ;; Section 4 of RFC 4648 [20] (note: the Base64 output MUST NOT
243 ;; include whitespace and MUST set padding bits to zero). [21]
244 (base64-encode-string (jabber-caps--secure-hash algorithm s) t))))
246 (defun jabber-caps--secure-hash (algorithm string)
248 ;; `secure-hash' was introduced in Emacs 24
249 ((fboundp 'secure-hash)
250 (secure-hash algorithm string nil nil t))
251 ((eq algorithm 'sha1)
252 ;; For SHA-1, we can use the `sha1' function.
253 (sha1 string nil nil t))
255 (error "Cannot use hash algorithm %s!" algorithm))))
257 (defun jabber-caps-identity-< (a b)
258 (let ((a-category (jabber-xml-get-attribute a 'category))
259 (b-category (jabber-xml-get-attribute b 'category)))
260 (or (string< a-category b-category)
261 (and (string= a-category b-category)
262 (let ((a-type (jabber-xml-get-attribute a 'type))
263 (b-type (jabber-xml-get-attribute b 'type)))
264 (or (string< a-type b-type)
265 (and (string= a-type b-type)
266 (let ((a-xml:lang (jabber-xml-get-attribute a 'xml:lang))
267 (b-xml:lang (jabber-xml-get-attribute b 'xml:lang)))
268 (string< a-xml:lang b-xml:lang)))))))))
270 (defvar jabber-caps-default-hash-function "sha-1"
271 "Hash function to use when sending caps in presence stanzas.
272 The value should be a key in `jabber-caps-hash-names'.")
274 (defvar jabber-caps-current-hash nil
275 "The current disco hash we're sending out in presence stanzas.")
277 (defconst jabber-caps-node "http://emacs-jabber.sourceforge.net")
280 (defun jabber-disco-advertise-feature (feature)
281 (unless (member feature jabber-advertised-features)
282 (push feature jabber-advertised-features)
283 (when jabber-caps-current-hash
284 (jabber-caps-recalculate-hash)
285 ;; If we're already connected, we need to send updated presence
286 ;; for the new feature.
287 (mapc #'jabber-send-current-presence jabber-connections))))
289 (defun jabber-caps-recalculate-hash ()
290 "Update `jabber-caps-current-hash' for feature list change.
291 Also update `jabber-disco-info-nodes', so we return results for
293 (let* ((old-hash jabber-caps-current-hash)
294 (old-node (and old-hash (concat jabber-caps-node "#" old-hash)))
296 (jabber-caps-ver-string `(query () ,@(jabber-disco-return-client-info))
297 jabber-caps-default-hash-function))
298 (new-node (concat jabber-caps-node "#" new-hash)))
300 (let ((old-entry (assoc old-node jabber-disco-info-nodes)))
302 (setq jabber-disco-info-nodes (delq old-entry jabber-disco-info-nodes)))))
303 (push (list new-node #'jabber-disco-return-client-info nil)
304 jabber-disco-info-nodes)
305 (setq jabber-caps-current-hash new-hash)))
308 (defun jabber-caps-presence-element (_jc)
309 (unless jabber-caps-current-hash
310 (jabber-caps-recalculate-hash))
313 `(c ((xmlns . "http://jabber.org/protocol/caps")
314 (hash . ,jabber-caps-default-hash-function)
315 (node . ,jabber-caps-node)
316 (ver . ,jabber-caps-current-hash)))))
319 (eval-after-load "jabber-presence"
320 '(add-to-list 'jabber-presence-element-functions #'jabber-caps-presence-element))
322 (defvar jabber-advertised-features
323 (list "http://jabber.org/protocol/disco#info")
324 "Features advertised on service discovery requests.
326 Don't add your feature to this list directly. Instead, call
327 `jabber-disco-advertise-feature'.")
329 (defvar jabber-disco-items-nodes
332 "Alist of node names and information about returning disco item data.
333 Key is node name as a string, or \"\" for no node specified. Value is
336 First item is data to return. If it is a function, that function is
337 called and its return value is used; if it is a list, that list is
338 used. The list should be the XML data to be returned inside the
339 <query/> element, like this:
341 \((item ((name . \"Name of first item\")
342 (jid . \"first.item\")
345 Second item is access control function. That function is passed the
346 JID, and returns non-nil if access is granted. If the second item is
347 nil, access is always granted.")
349 (defvar jabber-disco-info-nodes
351 (list "" #'jabber-disco-return-client-info nil))
352 "Alist of node names and information returning disco info data.
353 Key is node name as a string, or \"\" for no node specified. Value is
356 First item is data to return. If it is a function, that function is
357 called and its return value is used; if it is a list, that list is
358 used. The list should be the XML data to be returned inside the
359 <query/> element, like this:
361 \((identity ((category . \"client\")
363 (name . \"Jabber client\")))
364 (feature ((var . \"some-feature\"))))
366 Second item is access control function. That function is passed the
367 JID, and returns non-nil if access is granted. If the second item is
368 nil, access is always granted.")
370 (add-to-list 'jabber-iq-get-xmlns-alist
371 (cons "http://jabber.org/protocol/disco#info" 'jabber-return-disco-info))
372 (add-to-list 'jabber-iq-get-xmlns-alist
373 (cons "http://jabber.org/protocol/disco#items" 'jabber-return-disco-info))
374 (defun jabber-return-disco-info (jc xml-data)
375 "Respond to a service discovery request.
378 JC is the Jabber connection.
379 XML-DATA is the parsed tree data from the stream (stanzas)
380 obtained from `xml-parse-region'."
381 (let* ((to (jabber-xml-get-attribute xml-data 'from))
382 (id (jabber-xml-get-attribute xml-data 'id))
383 (xmlns (jabber-iq-xmlns xml-data))
384 (which-alist (eval (cdr (assoc xmlns
386 (cons "http://jabber.org/protocol/disco#info" 'jabber-disco-info-nodes)
387 (cons "http://jabber.org/protocol/disco#items" 'jabber-disco-items-nodes))))))
389 (jabber-xml-get-attribute (jabber-iq-query xml-data) 'node)
391 (return-list (cdr (assoc node which-alist)))
392 (func (nth 0 return-list))
393 (access-control (nth 1 return-list)))
395 (if (and (functionp access-control)
396 (not (funcall access-control jc to)))
397 (jabber-signal-error "Cancel" 'not-allowed)
398 ;; Access control passed
399 (let ((result (if (functionp func)
400 (funcall func jc xml-data)
402 (jabber-send-iq jc to "result"
403 `(query ((xmlns . ,xmlns)
405 (list (cons 'node node))))
407 nil nil nil nil id)))
410 (jabber-signal-error "Cancel" 'item-not-found))))
412 (defun jabber-disco-return-client-info (&optional jc xml-data)
414 ;; If running under a window system, this is
415 ;; a GUI client. If not, it is a console client.
416 (identity ((category . "client")
417 (name . "Emacs Jabber client")
418 (type . ,(if (memq window-system
423 #'(lambda (featurename)
424 `(feature ((var . ,featurename))))
425 jabber-advertised-features)))
427 (add-to-list 'jabber-jid-info-menu
428 (cons "Send items disco query" 'jabber-get-disco-items))
429 (defun jabber-get-disco-items (jc to &optional node)
430 "Send a service discovery request for items.
432 JC is the Jabber connection."
433 (interactive (list (jabber-read-account)
434 (jabber-read-jid-completing "Send items disco request to: " nil nil nil 'full t)
435 (jabber-read-node "Node (or leave empty): ")))
436 (jabber-send-iq jc to
438 (list 'query (append (list (cons 'xmlns "http://jabber.org/protocol/disco#items"))
439 (if (> (length node) 0)
440 (list (cons 'node node)))))
441 #'jabber-process-data #'jabber-process-disco-items
442 #'jabber-process-data "Item discovery failed"))
444 (add-to-list 'jabber-jid-info-menu
445 (cons "Send info disco query" 'jabber-get-disco-info))
446 (defun jabber-get-disco-info (jc to &optional node)
447 "Send a service discovery request for info.
449 JC is the Jabber connection."
450 (interactive (list (jabber-read-account)
451 (jabber-read-jid-completing "Send info disco request to: " nil nil nil 'full t)
452 (jabber-read-node "Node (or leave empty): ")))
453 (jabber-send-iq jc to
455 (list 'query (append (list (cons 'xmlns "http://jabber.org/protocol/disco#info"))
456 (if (> (length node) 0)
457 (list (cons 'node node)))))
458 #'jabber-process-data #'jabber-process-disco-info
459 #'jabber-process-data "Info discovery failed"))
461 (defun jabber-process-disco-info (jc xml-data)
462 "Handle results from info disco requests.
464 JC is the Jabber connection.
465 XML-DATA is the parsed tree data from the stream (stanzas)
466 obtained from `xml-parse-region'."
468 (let ((beginning (point)))
469 (dolist (x (jabber-xml-node-children (jabber-iq-query xml-data)))
471 ((eq (jabber-xml-node-name x) 'identity)
472 (let ((name (jabber-xml-get-attribute x 'name))
473 (category (jabber-xml-get-attribute x 'category))
474 (type (jabber-xml-get-attribute x 'type)))
475 (insert (jabber-propertize (if name
478 'face 'jabber-title-medium)
479 "\n\nCategory:\t" category "\n")
481 (insert "Type:\t\t" type "\n"))
483 ((eq (jabber-xml-node-name x) 'feature)
484 (let ((var (jabber-xml-get-attribute x 'var)))
485 (insert "Feature:\t" var "\n")))))
486 (put-text-property beginning (point)
487 'jabber-jid (jabber-xml-get-attribute xml-data 'from))
488 (put-text-property beginning (point)
489 'jabber-account jc)))
491 (defun jabber-process-disco-items (jc xml-data)
492 "Handle results from items disco requests.
494 JC is the Jabber connection.
495 XML-DATA is the parsed tree data from the stream (stanzas)
496 obtained from `xml-parse-region'."
498 (let ((items (jabber-xml-get-children (jabber-iq-query xml-data) 'item)))
501 (let ((jid (jabber-xml-get-attribute item 'jid))
502 (name (jabber-xml-get-attribute item 'name))
503 (node (jabber-xml-get-attribute item 'node)))
508 (concat jid "\n" (if node (format "Node: %s\n" node)))
509 'face 'jabber-title-medium)
513 'jabber-node node))))
514 (insert "No items found.\n"))))
516 ;; Keys are ("jid" . "node"), where "node" is nil if appropriate.
517 ;; Values are (identities features), where each identity is ["name"
518 ;; "category" "type"], and each feature is a string.
519 (defvar jabber-disco-info-cache (make-hash-table :test 'equal))
521 ;; Keys are ("jid" . "node"). Values are (items), where each
522 ;; item is ["name" "jid" "node"] (some values may be nil).
523 (defvar jabber-disco-items-cache (make-hash-table :test 'equal))
525 (defun jabber-disco-get-info (jc jid node callback closure-data &optional force)
526 "Get disco info for JID and NODE, using connection JC.
527 Call CALLBACK with JC and CLOSURE-DATA as first and second
528 arguments and result as third argument when result is available.
529 On success, result is (IDENTITIES FEATURES), where each identity is [\"name\"
530 \"category\" \"type\"], and each feature is a string.
531 On error, result is the error node, recognizable by (eq (car result) 'error).
533 If CALLBACK is nil, just fetch data. If FORCE is non-nil,
534 invalidate cache and get fresh data."
536 (remhash (cons jid node) jabber-disco-info-cache))
537 (let ((result (unless force (jabber-disco-get-info-immediately jid node))))
539 (and callback (run-with-timer 0 nil callback jc closure-data result))
540 (jabber-send-iq jc jid
542 `(query ((xmlns . "http://jabber.org/protocol/disco#info")
543 ,@(when node `((node . ,node)))))
544 #'jabber-disco-got-info (cons callback closure-data)
545 (lambda (jc xml-data callback-data)
546 (when (car callback-data)
547 (funcall (car callback-data) jc (cdr callback-data) (jabber-iq-error xml-data))))
548 (cons callback closure-data)))))
550 (defun jabber-disco-got-info (jc xml-data callback-data)
551 (let ((jid (jabber-xml-get-attribute xml-data 'from))
552 (node (jabber-xml-get-attribute (jabber-iq-query xml-data)
554 (result (jabber-disco-parse-info xml-data)))
555 (puthash (cons jid node) result jabber-disco-info-cache)
556 (when (car callback-data)
557 (funcall (car callback-data) jc (cdr callback-data) result))))
559 (defun jabber-disco-parse-info (xml-data)
560 "Extract data from an <iq/> stanza containing a disco#info result.
561 See `jabber-disco-get-info' for a description of the return value.
563 XML-DATA is the parsed tree data from the stream (stanzas)
564 obtained from `xml-parse-region'."
568 (vector (jabber-xml-get-attribute id 'name)
569 (jabber-xml-get-attribute id 'category)
570 (jabber-xml-get-attribute id 'type)))
571 (jabber-xml-get-children (jabber-iq-query xml-data) 'identity))
574 (jabber-xml-get-attribute feature 'var))
575 (jabber-xml-get-children (jabber-iq-query xml-data) 'feature))))
577 (defun jabber-disco-get-info-immediately (jid node)
578 "Get cached disco info for JID and NODE.
579 Return nil if no info available.
581 Fill the cache with `jabber-disco-get-info'."
583 ;; Check "normal" cache...
584 (gethash (cons jid node) jabber-disco-info-cache)
585 ;; And then check Entity Capabilities.
586 (and (null node) (jabber-caps-get-cached jid))))
588 (defun jabber-disco-get-items (jc jid node callback closure-data &optional force)
589 "Get disco items for JID and NODE, using connection JC.
590 Call CALLBACK with JC and CLOSURE-DATA as first and second
591 arguments and items result as third argument when result is
593 On success, result is a list of items, where each
594 item is [\"name\" \"jid\" \"node\"] (some values may be nil).
595 On error, result is the error node, recognizable by (eq (car result) 'error).
597 If CALLBACK is nil, just fetch data. If FORCE is non-nil,
598 invalidate cache and get fresh data."
600 (remhash (cons jid node) jabber-disco-items-cache))
601 (let ((result (gethash (cons jid node) jabber-disco-items-cache)))
603 (and callback (run-with-timer 0 nil callback jc closure-data result))
604 (jabber-send-iq jc jid
606 `(query ((xmlns . "http://jabber.org/protocol/disco#items")
607 ,@(when node `((node . ,node)))))
608 #'jabber-disco-got-items (cons callback closure-data)
609 (lambda (jc xml-data callback-data)
610 (when (car callback-data)
611 (funcall (car callback-data) jc (cdr callback-data) (jabber-iq-error xml-data))))
612 (cons callback closure-data)))))
614 (defun jabber-disco-got-items (jc xml-data callback-data)
615 (let ((jid (jabber-xml-get-attribute xml-data 'from))
616 (node (jabber-xml-get-attribute (jabber-iq-query xml-data)
622 (jabber-xml-get-attribute item 'name)
623 (jabber-xml-get-attribute item 'jid)
624 (jabber-xml-get-attribute item 'node)))
625 (jabber-xml-get-children (jabber-iq-query xml-data) 'item))))
626 (puthash (cons jid node) result jabber-disco-items-cache)
627 (when (car callback-data)
628 (funcall (car callback-data) jc (cdr callback-data) result))))
630 (defun jabber-disco-get-items-immediately (jid node)
631 (gethash (cons jid node) jabber-disco-items-cache))
633 (defun jabber-disco-publish (jc node item-name item-jid item-node)
634 "Publish the given item under disco node NODE."
635 (jabber-send-iq jc nil
637 `(query ((xmlns . "http://jabber.org/protocol/disco#items")
638 ,@(when node `((node . ,node))))
639 (item ((action . "update")
642 `((name . ,item-name)))
644 `((node . ,item-node))))))
645 'jabber-report-success "Disco publish"
646 'jabber-report-success "Disco publish"))
648 (defun jabber-disco-publish-remove (jc node item-jid item-node)
649 "Remove the given item from published disco items.
651 JC is the Jabber connection."
652 (jabber-send-iq jc nil
654 `(query ((xmlns . "http://jabber.org/protocol/disco#items")
655 ,@(when node `((node . ,node))))
656 (item ((action . "remove")
659 `((node . ,item-node))))))
660 'jabber-report-success "Disco removal"
661 'jabber-report-success "Disco removal"))
663 (provide 'jabber-disco)
665 ;;; arch-tag: 71f5c76f-2956-4ed2-b871-9f5fe198092d