plugin XEP-0050: some modernisation + adHocSequence:
improved code by reordering imports, adding some type hints, using standard dict instead of OrderedDict, etc. New sequence method/adHocSequence bridge method allow to easily send a sequence of data to a well known ad-hoc node.
This commit is contained in:
parent
86aaf35b60
commit
762f96acb2
1 changed files with 102 additions and 45 deletions
|
|
@ -17,34 +17,30 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from sat.core.i18n import _, D_
|
||||
from sat.core.constants import Const as C
|
||||
from sat.core.log import getLogger
|
||||
|
||||
log = getLogger(__name__)
|
||||
from collections import namedtuple
|
||||
from uuid import uuid4
|
||||
from typing import List, Optional
|
||||
|
||||
from zope.interface import implementer
|
||||
from twisted.words.protocols.jabber import jid
|
||||
from twisted.words.protocols import jabber
|
||||
from twisted.words.protocols.jabber.xmlstream import XMPPHandler
|
||||
from twisted.words.xish import domish
|
||||
from twisted.internet import defer
|
||||
from wokkel import disco, iwokkel, data_form
|
||||
from sat.core.i18n import _, D_
|
||||
from sat.core.constants import Const as C
|
||||
from sat.core.log import getLogger
|
||||
from sat.core.xmpp import SatXMPPEntity
|
||||
from sat.core import exceptions
|
||||
from sat.memory.memory import Sessions
|
||||
from uuid import uuid4
|
||||
from sat.tools import xml_tools
|
||||
from sat.tools.common import data_format
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
try:
|
||||
from twisted.words.protocols.xmlstream import XMPPHandler
|
||||
except ImportError:
|
||||
from wokkel.subprotocols import XMPPHandler
|
||||
log = getLogger(__name__)
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
try:
|
||||
from collections import OrderedDict # only available from python 2.7
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
IQ_SET = '/iq[@type="set"]'
|
||||
NS_COMMANDS = "http://jabber.org/protocol/commands"
|
||||
|
|
@ -52,16 +48,14 @@ ID_CMD_LIST = disco.DiscoIdentity("automation", "command-list")
|
|||
ID_CMD_NODE = disco.DiscoIdentity("automation", "command-node")
|
||||
CMD_REQUEST = IQ_SET + '/command[@xmlns="' + NS_COMMANDS + '"]'
|
||||
|
||||
SHOWS = OrderedDict(
|
||||
[
|
||||
("default", _("Online")),
|
||||
("away", _("Away")),
|
||||
("chat", _("Free for chat")),
|
||||
("dnd", _("Do not disturb")),
|
||||
("xa", _("Left")),
|
||||
("disconnect", _("Disconnect")),
|
||||
]
|
||||
)
|
||||
SHOWS = {
|
||||
"default": _("Online"),
|
||||
"away": _("Away"),
|
||||
"chat": _("Free for chat"),
|
||||
"dnd": _("Do not disturb"),
|
||||
"xa": _("Left"),
|
||||
"disconnect": _("Disconnect"),
|
||||
}
|
||||
|
||||
PLUGIN_INFO = {
|
||||
C.PI_NAME: "Ad-Hoc Commands",
|
||||
|
|
@ -209,6 +203,15 @@ class AdHocCommand(XMPPHandler):
|
|||
self.client.send(iq_elt)
|
||||
del self.sessions[session_id]
|
||||
|
||||
def _requestEb(self, failure_, request, session_id):
|
||||
if failure_.check(AdHocError):
|
||||
error_constant = failure.value.callback_error
|
||||
else:
|
||||
log.error(f"unexpected error while handling request: {failure_}")
|
||||
error_constant = XEP_0050.ERROR.INTERNAL
|
||||
|
||||
self._sendError(error_constant, session_id, request)
|
||||
|
||||
def onRequest(self, command_elt, requestor, action, session_id):
|
||||
if not self.isAuthorised(requestor):
|
||||
return self._sendError(
|
||||
|
|
@ -240,12 +243,7 @@ class AdHocCommand(XMPPHandler):
|
|||
self.node,
|
||||
)
|
||||
d.addCallback(self._sendAnswer, session_id, command_elt.parent)
|
||||
d.addErrback(
|
||||
lambda failure, request: self._sendError(
|
||||
failure.value.callback_error, session_id, request
|
||||
),
|
||||
command_elt.parent,
|
||||
)
|
||||
d.addErrback(self._requestEb, command_elt.parent, session_id)
|
||||
|
||||
|
||||
class XEP_0050(object):
|
||||
|
|
@ -303,6 +301,14 @@ class XEP_0050(object):
|
|||
method=self._listUI,
|
||||
async_=True,
|
||||
)
|
||||
host.bridge.addMethod(
|
||||
"adHocSequence",
|
||||
".plugin",
|
||||
in_sign="ssss",
|
||||
out_sign="s",
|
||||
method=self._sequence,
|
||||
async_=True,
|
||||
)
|
||||
self.__requesting_id = host.registerCallback(
|
||||
self._requestingEntity, with_data=True
|
||||
)
|
||||
|
|
@ -334,7 +340,7 @@ class XEP_0050(object):
|
|||
None if no session is involved
|
||||
@param form_values(dict, None): values to use to create command form
|
||||
values will be passed to data_form.Form.makeFields
|
||||
@return
|
||||
@return: iq result element
|
||||
"""
|
||||
iq_elt = client.IQ(timeout=timeout)
|
||||
iq_elt["to"] = entity.full()
|
||||
|
|
@ -405,26 +411,12 @@ class XEP_0050(object):
|
|||
}
|
||||
return ["%s%s" % (lvl_map[lvl], msg) for lvl, msg in notes]
|
||||
|
||||
def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data):
|
||||
"""Convert command answer to an ui for frontend
|
||||
|
||||
@param iq_elt: command result
|
||||
@param session_id: id of the session used with the frontend
|
||||
@param profile_key: %(doc_profile_key)s
|
||||
"""
|
||||
def parseCommandAnswer(self, iq_elt):
|
||||
command_elt = self.getCommandElt(iq_elt)
|
||||
status = command_elt.getAttribute("status", XEP_0050.STATUS.EXECUTING)
|
||||
if status in [XEP_0050.STATUS.COMPLETED, XEP_0050.STATUS.CANCELED]:
|
||||
# the command session is finished, we purge our session
|
||||
del self.requesting[session_id]
|
||||
if status == XEP_0050.STATUS.COMPLETED:
|
||||
session_id = None
|
||||
else:
|
||||
return None
|
||||
remote_session_id = command_elt.getAttribute("sessionid")
|
||||
if remote_session_id:
|
||||
session_data["remote_id"] = remote_session_id
|
||||
notes = []
|
||||
data = {}
|
||||
data["status"] = command_elt.getAttribute("status", XEP_0050.STATUS.EXECUTING)
|
||||
data["session_id"] = command_elt.getAttribute("sessionid")
|
||||
data["notes"] = notes = []
|
||||
for note_elt in command_elt.elements(NS_COMMANDS, "note"):
|
||||
notes.append(
|
||||
(
|
||||
|
|
@ -432,6 +424,30 @@ class XEP_0050(object):
|
|||
str(note_elt),
|
||||
)
|
||||
)
|
||||
|
||||
return command_elt, data
|
||||
|
||||
|
||||
def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data):
|
||||
"""Convert command answer to an ui for frontend
|
||||
|
||||
@param iq_elt: command result
|
||||
@param session_id: id of the session used with the frontend
|
||||
@param profile_key: %(doc_profile_key)s
|
||||
"""
|
||||
command_elt, answer_data = self.parseCommandAnswer(iq_elt)
|
||||
status = answer_data["status"]
|
||||
if status in [XEP_0050.STATUS.COMPLETED, XEP_0050.STATUS.CANCELED]:
|
||||
# the command session is finished, we purge our session
|
||||
del self.requesting[session_id]
|
||||
if status == XEP_0050.STATUS.COMPLETED:
|
||||
session_id = None
|
||||
else:
|
||||
return None
|
||||
remote_session_id = answer_data["session_id"]
|
||||
if remote_session_id:
|
||||
session_data["remote_id"] = remote_session_id
|
||||
notes = answer_data["notes"]
|
||||
for data_elt in command_elt.elements(data_form.NS_X_DATA, "x"):
|
||||
if data_elt["type"] in ("form", "result"):
|
||||
break
|
||||
|
|
@ -608,7 +624,7 @@ class XEP_0050(object):
|
|||
|
||||
@defer.inlineCallbacks
|
||||
def run(self, client, service_jid=None, node=None):
|
||||
"""run an ad-hoc command
|
||||
"""Run an ad-hoc command
|
||||
|
||||
@param service_jid(jid.JID, None): jid of the ad-hoc service
|
||||
None to use profile's server
|
||||
|
|
@ -661,6 +677,47 @@ class XEP_0050(object):
|
|||
d.addCallback(self._items2XMLUI, no_instructions)
|
||||
return d
|
||||
|
||||
def _sequence(self, sequence, node, service_jid_s="", profile_key=C.PROF_KEY_NONE):
|
||||
sequence = data_format.deserialise(sequence, type_check=list)
|
||||
client = self.host.getClient(profile_key)
|
||||
service_jid = jid.JID(service_jid_s) if service_jid_s else None
|
||||
d = defer.ensureDeferred(self.sequence(client, sequence, node, service_jid))
|
||||
d.addCallback(lambda data: data_format.serialise(data))
|
||||
return d
|
||||
|
||||
async def sequence(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
sequence: List[dict],
|
||||
node: str,
|
||||
service_jid: Optional[jid.JID] = None,
|
||||
) -> dict:
|
||||
"""Send a series of data to an ad-hoc service
|
||||
|
||||
@param sequence: list of
|
||||
@param node: node of the ad-hoc commnad
|
||||
@param service_jid: jid of the ad-hoc service
|
||||
None to use profile's server
|
||||
@return: data received in final answer
|
||||
"""
|
||||
if service_jid is None:
|
||||
service_jid = jid.JID(client.jid.host)
|
||||
|
||||
session_id = None
|
||||
|
||||
for data_to_send in sequence:
|
||||
iq_result_elt = await self.do(
|
||||
client,
|
||||
service_jid,
|
||||
node,
|
||||
session_id=session_id,
|
||||
form_values=data_to_send,
|
||||
)
|
||||
__, answer_data = self.parseCommandAnswer(iq_result_elt)
|
||||
session_id = answer_data.pop("session_id")
|
||||
|
||||
return answer_data
|
||||
|
||||
def addAdHocCommand(self, client, callback, label, node=None, features=None,
|
||||
timeout=600, allowed_jids=None, allowed_groups=None,
|
||||
allowed_magics=None, forbidden_jids=None, forbidden_groups=None,
|
||||
|
|
|
|||
Loading…
Reference in a new issue