;; jabber-ahc.el - Ad-Hoc Commands by JEP-0050 -*- lexical-binding: t; -*- ;; 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) (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 "Alist of ad-hoc commands provided. The keys are node names as strings (which means that they must not conflict). The values are plists having the 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 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 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"))))))) (jabber-disco-advertise-feature "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. JC is the Jabber connection. XML-DATA is the parsed tree data from the stream (stanzas) obtained from `xml-parse-region'." (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. See XEP-0050. JC is the Jabber connection." (interactive (list (jabber-read-account) (jabber-read-jid-completing "Request command list from: " nil nil nil nil nil))) (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. See XEP-0050. JC is the Jabber connection." (interactive (list (jabber-read-account) (jabber-read-jid-completing "Execute command of: " nil nil nil nil nil) (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