Project: jabber.el
Code Location: git://emacs-jabber.git.sourceforge.net/gitroot/emacs-jabber/emacs-jabbermaster
Browse
/
Download File
jabber-ahc.el
;; jabber-ahc.el - Ad-Hoc Commands by JEP-0050

;; Copyright (C) 2003, 2004, 2007, 2008 - Magnus Henoch - mange@freemail.hu
;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net

;; 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)
(require 'jabber-autoloads)

(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 connection object and JID of requester,
	  returning non-nil for access allowed.  No function means
          open for everyone.
name	- name of command
func	- function taking connection object and entire IQ stanza as 
          arguments 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 (jc 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 jc jid))
		     `(item ((name . ,name)
			     (jid . ,(jabber-connection-jid jc))
			     (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 (jc 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 jc to))
	      ;; access control passed
	      (jabber-send-iq jc to "result"
			      (funcall func jc 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 (jc to)
  "Request list of ad-hoc commands.  (JEP-0050)"
  (interactive (list (jabber-read-account)
		     (jabber-read-jid-completing "Request command list from: " nil nil nil 'full t)))
  (jabber-get-disco-items jc 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 (jc to node)
  "Execute ad-hoc command.  (JEP-0050)"
  (interactive (list (jabber-read-account)
		     (jabber-read-jid-completing "Execute command of: " nil nil nil 'full t)
		     (jabber-read-node "Node of command: ")))
  (jabber-send-iq jc 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 (jc 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)
    (make-local-variable 'jabber-buffer-connection)
    (setq jabber-buffer-connection jc)

    (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-buffer-connection 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