slidge 0.1.0rc1__py3-none-any.whl → 0.1.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. slidge/__init__.py +54 -31
  2. slidge/__main__.py +51 -5
  3. slidge/command/__init__.py +28 -0
  4. slidge/command/adhoc.py +258 -0
  5. slidge/command/admin.py +193 -0
  6. slidge/command/base.py +441 -0
  7. slidge/command/categories.py +3 -0
  8. slidge/command/chat_command.py +288 -0
  9. slidge/command/register.py +179 -0
  10. slidge/command/user.py +250 -0
  11. slidge/contact/__init__.py +8 -0
  12. slidge/contact/contact.py +452 -0
  13. slidge/contact/roster.py +192 -0
  14. slidge/core/__init__.py +2 -0
  15. slidge/core/cache.py +121 -39
  16. slidge/core/config.py +116 -11
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +895 -0
  19. slidge/core/gateway/caps.py +63 -0
  20. slidge/core/gateway/delivery_receipt.py +52 -0
  21. slidge/core/gateway/disco.py +80 -0
  22. slidge/core/gateway/mam.py +75 -0
  23. slidge/core/gateway/muc_admin.py +35 -0
  24. slidge/core/gateway/ping.py +66 -0
  25. slidge/core/gateway/presence.py +95 -0
  26. slidge/core/gateway/registration.py +53 -0
  27. slidge/core/gateway/search.py +102 -0
  28. slidge/core/gateway/session_dispatcher.py +795 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +9 -1
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +6 -19
  34. slidge/core/mixins/disco.py +66 -15
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +254 -252
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +128 -31
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +275 -116
  41. slidge/core/session.py +586 -518
  42. slidge/group/__init__.py +10 -0
  43. slidge/group/archive.py +125 -0
  44. slidge/group/bookmarks.py +163 -0
  45. slidge/group/participant.py +458 -0
  46. slidge/group/room.py +1103 -0
  47. slidge/migration.py +18 -0
  48. slidge/slixfix/__init__.py +68 -0
  49. slidge/{util/xep_0050 → slixfix/link_preview}/__init__.py +4 -5
  50. slidge/slixfix/link_preview/link_preview.py +17 -0
  51. slidge/slixfix/link_preview/stanza.py +99 -0
  52. slidge/slixfix/roster.py +60 -0
  53. slidge/{util → slixfix}/xep_0077/register.py +1 -2
  54. slidge/slixfix/xep_0077/stanza.py +104 -0
  55. slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
  56. slidge/slixfix/xep_0153/__init__.py +10 -0
  57. slidge/slixfix/xep_0153/stanza.py +25 -0
  58. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  59. slidge/slixfix/xep_0264/__init__.py +5 -0
  60. slidge/slixfix/xep_0264/stanza.py +36 -0
  61. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  62. slidge/slixfix/xep_0292/__init__.py +5 -0
  63. slidge/slixfix/xep_0292/vcard4.py +100 -0
  64. slidge/slixfix/xep_0313/__init__.py +12 -0
  65. slidge/slixfix/xep_0313/mam.py +262 -0
  66. slidge/slixfix/xep_0313/stanza.py +359 -0
  67. slidge/slixfix/xep_0317/__init__.py +5 -0
  68. slidge/slixfix/xep_0317/hats.py +17 -0
  69. slidge/slixfix/xep_0317/stanza.py +28 -0
  70. slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
  71. slidge/slixfix/xep_0424/__init__.py +9 -0
  72. slidge/slixfix/xep_0424/retraction.py +77 -0
  73. slidge/slixfix/xep_0424/stanza.py +28 -0
  74. slidge/slixfix/xep_0490/__init__.py +8 -0
  75. slidge/slixfix/xep_0490/mds.py +47 -0
  76. slidge/slixfix/xep_0490/stanza.py +17 -0
  77. slidge/util/__init__.py +4 -6
  78. slidge/util/archive_msg.py +61 -0
  79. slidge/util/conf.py +25 -4
  80. slidge/util/db.py +23 -69
  81. slidge/util/schema.sql +126 -0
  82. slidge/util/sql.py +508 -0
  83. slidge/util/test.py +136 -86
  84. slidge/util/types.py +155 -14
  85. slidge/util/util.py +225 -51
  86. slidge-0.1.2.dist-info/METADATA +111 -0
  87. slidge-0.1.2.dist-info/RECORD +96 -0
  88. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
  89. slidge/core/adhoc.py +0 -492
  90. slidge/core/chat_command.py +0 -197
  91. slidge/core/contact.py +0 -441
  92. slidge/core/disco.py +0 -59
  93. slidge/core/gateway.py +0 -899
  94. slidge/core/muc/__init__.py +0 -3
  95. slidge/core/muc/bookmarks.py +0 -74
  96. slidge/core/muc/participant.py +0 -152
  97. slidge/core/muc/room.py +0 -348
  98. slidge/plugins/discord/__init__.py +0 -121
  99. slidge/plugins/discord/client.py +0 -121
  100. slidge/plugins/discord/session.py +0 -172
  101. slidge/plugins/dummy.py +0 -334
  102. slidge/plugins/facebook.py +0 -591
  103. slidge/plugins/hackernews.py +0 -209
  104. slidge/plugins/mattermost/__init__.py +0 -1
  105. slidge/plugins/mattermost/api.py +0 -288
  106. slidge/plugins/mattermost/gateway.py +0 -417
  107. slidge/plugins/mattermost/websocket.py +0 -248
  108. slidge/plugins/signal/__init__.py +0 -4
  109. slidge/plugins/signal/config.py +0 -4
  110. slidge/plugins/signal/contact.py +0 -104
  111. slidge/plugins/signal/gateway.py +0 -379
  112. slidge/plugins/signal/group.py +0 -76
  113. slidge/plugins/signal/session.py +0 -515
  114. slidge/plugins/signal/txt.py +0 -13
  115. slidge/plugins/signal/util.py +0 -32
  116. slidge/plugins/skype.py +0 -310
  117. slidge/plugins/steam.py +0 -400
  118. slidge/plugins/telegram/__init__.py +0 -6
  119. slidge/plugins/telegram/client.py +0 -325
  120. slidge/plugins/telegram/config.py +0 -21
  121. slidge/plugins/telegram/contact.py +0 -154
  122. slidge/plugins/telegram/gateway.py +0 -182
  123. slidge/plugins/telegram/group.py +0 -184
  124. slidge/plugins/telegram/session.py +0 -275
  125. slidge/plugins/telegram/util.py +0 -153
  126. slidge/plugins/whatsapp/__init__.py +0 -6
  127. slidge/plugins/whatsapp/config.py +0 -17
  128. slidge/plugins/whatsapp/contact.py +0 -33
  129. slidge/plugins/whatsapp/event.go +0 -455
  130. slidge/plugins/whatsapp/gateway.go +0 -156
  131. slidge/plugins/whatsapp/gateway.py +0 -69
  132. slidge/plugins/whatsapp/go.mod +0 -17
  133. slidge/plugins/whatsapp/go.sum +0 -22
  134. slidge/plugins/whatsapp/session.go +0 -371
  135. slidge/plugins/whatsapp/session.py +0 -370
  136. slidge/util/xep_0030/__init__.py +0 -13
  137. slidge/util/xep_0030/disco.py +0 -811
  138. slidge/util/xep_0030/stanza/__init__.py +0 -7
  139. slidge/util/xep_0030/stanza/info.py +0 -270
  140. slidge/util/xep_0030/stanza/items.py +0 -147
  141. slidge/util/xep_0030/static.py +0 -467
  142. slidge/util/xep_0050/adhoc.py +0 -631
  143. slidge/util/xep_0050/stanza.py +0 -180
  144. slidge/util/xep_0077/stanza.py +0 -71
  145. slidge/util/xep_0292/__init__.py +0 -1
  146. slidge/util/xep_0292/stanza.py +0 -167
  147. slidge/util/xep_0292/vcard4.py +0 -74
  148. slidge/util/xep_0356/__init__.py +0 -7
  149. slidge/util/xep_0356/permissions.py +0 -35
  150. slidge/util/xep_0356/privilege.py +0 -160
  151. slidge/util/xep_0356/stanza.py +0 -44
  152. slidge/util/xep_0461/__init__.py +0 -6
  153. slidge/util/xep_0461/reply.py +0 -48
  154. slidge/util/xep_0461/stanza.py +0 -80
  155. slidge-0.1.0rc1.dist-info/METADATA +0 -171
  156. slidge-0.1.0rc1.dist-info/RECORD +0 -99
  157. /slidge/{plugins/__init__.py → py.typed} +0 -0
  158. /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
  159. /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
  160. /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
  161. /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
  162. /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
  163. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
  164. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -1,631 +0,0 @@
1
-
2
- # Slixmpp: The Slick XMPP Library
3
- # Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
4
- # This file is part of Slixmpp.
5
- # See the file LICENSE for copying permission.
6
- import asyncio
7
- import logging
8
- import time
9
-
10
- from slixmpp import Iq
11
- from slixmpp.exceptions import XMPPError
12
- from slixmpp.plugins import BasePlugin
13
- from slixmpp.plugins.xep_0004 import Form
14
- from slixmpp.xmlstream import JID, register_stanza_plugin
15
- from slixmpp.xmlstream.handler import Callback
16
- from slixmpp.xmlstream.matcher import StanzaPath
17
-
18
- from . import stanza
19
-
20
- Command = stanza.Command
21
-
22
- log = logging.getLogger(__name__)
23
-
24
-
25
- class XEP_0050(BasePlugin):
26
-
27
- """
28
- XEP-0050: Ad-Hoc Commands
29
-
30
- XMPP's Adhoc Commands provides a generic workflow mechanism for
31
- interacting with applications. The result is similar to menu selections
32
- and multi-step dialogs in normal desktop applications. Clients do not
33
- need to know in advance what commands are provided by any particular
34
- application or agent. While adhoc commands provide similar functionality
35
- to Jabber-RPC, adhoc commands are used primarily for human interaction.
36
-
37
- Also see <http://xmpp.org/extensions/xep-0050.html>
38
-
39
- Events:
40
- command_execute -- Received a command with action="execute"
41
- command_next -- Received a command with action="next"
42
- command_complete -- Received a command with action="complete"
43
- command_cancel -- Received a command with action="cancel"
44
-
45
- Attributes:
46
- commands -- A dictionary mapping JID/node pairs to command
47
- names and handlers.
48
- sessions -- A dictionary or equivalent backend mapping
49
- session IDs to dictionaries containing data
50
- relevant to a command's session.
51
-
52
- """
53
-
54
- name = 'xep_0050'
55
- description = 'XEP-0050: Ad-Hoc Commands (slidge)'
56
- dependencies = {'xep_0030', 'xep_0004'}
57
- stanza = stanza
58
- default_config = {
59
- 'session_db': None
60
- }
61
-
62
- def plugin_init(self):
63
- """Start the XEP-0050 plugin."""
64
- self.sessions = self.session_db
65
- if self.sessions is None:
66
- self.sessions = {}
67
-
68
- self.commands = {}
69
-
70
- self.xmpp.register_handler(
71
- Callback("Ad-Hoc Execute",
72
- StanzaPath('iq@type=set/command'),
73
- self._handle_command))
74
-
75
- register_stanza_plugin(Iq, Command)
76
- register_stanza_plugin(Command, Form, iterable=True)
77
-
78
- self.xmpp.add_event_handler('command', self._handle_command_all)
79
-
80
- def plugin_end(self):
81
- self.xmpp.del_event_handler('command', self._handle_command_all)
82
- self.xmpp.remove_handler('Ad-Hoc Execute')
83
- self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
84
- self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
85
-
86
- def session_bind(self, jid):
87
- self.xmpp['xep_0030'].add_feature(Command.namespace)
88
- # self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
89
-
90
- def set_backend(self, db):
91
- """
92
- Replace the default session storage dictionary with
93
- a generic, external data storage mechanism.
94
-
95
- The replacement backend must be able to interact through
96
- the same syntax and interfaces as a normal dictionary.
97
-
98
- :param db: The new session storage mechanism.
99
- """
100
- self.sessions = db
101
-
102
- def prep_handlers(self, handlers, **kwargs):
103
- """
104
- Prepare a list of functions for use by the backend service.
105
-
106
- Intended to be replaced by the backend service as needed.
107
-
108
- :param handlers: A list of function pointers
109
- :param kwargs: Any additional parameters required by the backend.
110
- """
111
- pass
112
-
113
- # =================================================================
114
- # Server side (command provider) API
115
-
116
- def add_command(self, jid=None, node=None, name='', handler=None):
117
- """
118
- Make a new command available to external entities.
119
-
120
- Access control may be implemented in the provided handler.
121
-
122
- Command workflow is done across a sequence of command handlers. The
123
- first handler is given the initial Iq stanza of the request in order
124
- to support access control. Subsequent handlers are given only the
125
- payload items of the command. All handlers will receive the command's
126
- session data.
127
-
128
- :param jid: The JID that will expose the command.
129
- :param node: The node associated with the command.
130
- :param name: A human readable name for the command.
131
- :param handler: A function that will generate the response to the
132
- initial command request, as well as enforcing any
133
- access control policies.
134
- """
135
- if jid is None:
136
- jid = self.xmpp.boundjid
137
- elif not isinstance(jid, JID):
138
- jid = JID(jid)
139
- item_jid = jid.full
140
-
141
- self.xmpp['xep_0030'].add_identity(category='automation',
142
- itype='command-list',
143
- name='Ad-Hoc commands',
144
- node=Command.namespace,
145
- jid=jid)
146
- self.xmpp['xep_0030'].add_item(jid=item_jid,
147
- name=name,
148
- node=Command.namespace,
149
- subnode=node,
150
- ijid=jid)
151
- self.xmpp['xep_0030'].add_identity(category='automation',
152
- itype='command-node',
153
- name=name,
154
- node=node,
155
- jid=jid)
156
- self.xmpp['xep_0030'].add_feature(Command.namespace, None, jid)
157
-
158
- self.commands[(item_jid, node)] = (name, handler)
159
-
160
- def new_session(self):
161
- """Return a new session ID."""
162
- return str(time.time()) + '-' + self.xmpp.new_id()
163
-
164
- def _handle_command(self, iq):
165
- """Raise command events based on the command action."""
166
- self.xmpp.event('command', iq)
167
- self.xmpp.event('command_%s' % iq['command']['action'], iq)
168
-
169
- async def _handle_command_all(self, iq: Iq) -> None:
170
- action = iq['command']['action']
171
- sessionid = iq['command']['sessionid']
172
- session = self.sessions.get(sessionid)
173
-
174
- if session is None:
175
- return await self._handle_command_start(iq)
176
-
177
- if action in ('next', 'execute'):
178
- return await self._handle_command_next(iq)
179
- if action == 'prev':
180
- return await self._handle_command_prev(iq)
181
- if action == 'complete':
182
- return await self._handle_command_complete(iq)
183
- if action == 'cancel':
184
- return await self._handle_command_cancel(iq)
185
- return None
186
-
187
- async def _handle_command_start(self, iq):
188
- """
189
- Process an initial request to execute a command.
190
-
191
- :param iq: The command execution request.
192
- """
193
- sessionid = self.new_session()
194
- node = iq['command']['node']
195
- key = (iq['to'].full, node)
196
- name, handler = self.commands.get(key, ('Not found', None))
197
- if not handler:
198
- log.debug('Command not found: %s, %s', key, self.commands)
199
- raise XMPPError('item-not-found')
200
-
201
- payload = []
202
- for stanza in iq['command']['substanzas']:
203
- payload.append(stanza)
204
-
205
- if len(payload) == 1:
206
- payload = payload[0]
207
-
208
- interfaces = {item.plugin_attrib for item in payload}
209
- payload_classes = {item.__class__ for item in payload}
210
-
211
- initial_session = {'id': sessionid,
212
- 'from': iq['from'],
213
- 'to': iq['to'],
214
- 'node': node,
215
- 'payload': payload,
216
- 'interfaces': interfaces,
217
- 'payload_classes': payload_classes,
218
- 'notes': None,
219
- 'has_next': False,
220
- 'allow_complete': False,
221
- 'allow_prev': False,
222
- 'past': [],
223
- 'next': None,
224
- 'prev': None,
225
- 'cancel': None}
226
-
227
- session = await _await_if_needed(handler, iq, initial_session)
228
-
229
- self._process_command_response(iq, session)
230
-
231
- async def _handle_command_next(self, iq):
232
- """
233
- Process a request for the next step in the workflow
234
- for a command with multiple steps.
235
-
236
- :param iq: The command continuation request.
237
- """
238
- sessionid = iq['command']['sessionid']
239
- session = self.sessions.get(sessionid)
240
-
241
- if session:
242
- handler = session['next']
243
- interfaces = session['interfaces']
244
- results = []
245
- for stanza in iq['command']['substanzas']:
246
- if stanza.plugin_attrib in interfaces:
247
- results.append(stanza)
248
- if len(results) == 1:
249
- results = results[0]
250
-
251
- session = await _await_if_needed(handler, results, session)
252
-
253
- self._process_command_response(iq, session)
254
- else:
255
- raise XMPPError('item-not-found')
256
-
257
- async def _handle_command_prev(self, iq):
258
- """
259
- Process a request for the prev step in the workflow
260
- for a command with multiple steps.
261
-
262
- :param iq: The command continuation request.
263
- """
264
- sessionid = iq['command']['sessionid']
265
- session = self.sessions.get(sessionid)
266
-
267
- if session:
268
- handler = session['prev']
269
- interfaces = session['interfaces']
270
- results = []
271
- for stanza in iq['command']['substanzas']:
272
- if stanza.plugin_attrib in interfaces:
273
- results.append(stanza)
274
- if len(results) == 1:
275
- results = results[0]
276
-
277
- session = await _await_if_needed(handler, results, session)
278
-
279
- self._process_command_response(iq, session)
280
- else:
281
- raise XMPPError('item-not-found')
282
-
283
- def _process_command_response(self, iq, session):
284
- """
285
- Generate a command reply stanza based on the
286
- provided session data.
287
-
288
- :param iq: The command request stanza.
289
- :param session: A dictionary of relevant session data.
290
- """
291
- sessionid = session['id']
292
-
293
- payload = session['payload']
294
- if payload is None:
295
- payload = []
296
- if not isinstance(payload, list):
297
- payload = [payload]
298
-
299
- interfaces = session.get('interfaces', set())
300
- payload_classes = session.get('payload_classes', set())
301
-
302
- interfaces.update({item.plugin_attrib for item in payload})
303
- payload_classes.update({item.__class__ for item in payload})
304
-
305
- session['interfaces'] = interfaces
306
- session['payload_classes'] = payload_classes
307
-
308
- self.sessions[sessionid] = session
309
-
310
- for item in payload:
311
- register_stanza_plugin(Command, item.__class__, iterable=True)
312
-
313
- iq = iq.reply()
314
- iq['command']['node'] = session['node']
315
- iq['command']['sessionid'] = session['id']
316
-
317
- if session['next'] is None:
318
- iq['command']['actions'] = []
319
- iq['command']['status'] = 'completed'
320
- elif session['has_next']:
321
- actions = ['next']
322
- if session['allow_complete']:
323
- actions.append('complete')
324
- if session['allow_prev']:
325
- actions.append('prev')
326
- iq['command']['actions'] = actions
327
- iq['command']['status'] = 'executing'
328
- else:
329
- iq['command']['actions'] = ['complete']
330
- iq['command']['status'] = 'executing'
331
-
332
- iq['command']['notes'] = session['notes']
333
-
334
- for item in payload:
335
- iq['command'].append(item)
336
-
337
- iq.send()
338
-
339
- async def _handle_command_cancel(self, iq):
340
- """
341
- Process a request to cancel a command's execution.
342
-
343
- :param iq: The command cancellation request.
344
- """
345
- node = iq['command']['node']
346
- sessionid = iq['command']['sessionid']
347
-
348
- session = self.sessions.get(sessionid)
349
-
350
- if session:
351
- handler = session['cancel']
352
- if handler:
353
- await _await_if_needed(handler, iq, session)
354
- del self.sessions[sessionid]
355
- iq = iq.reply()
356
- iq['command']['node'] = node
357
- iq['command']['sessionid'] = sessionid
358
- iq['command']['status'] = 'canceled'
359
- iq['command']['notes'] = session['notes']
360
- iq.send()
361
- else:
362
- raise XMPPError('item-not-found')
363
-
364
-
365
- async def _handle_command_complete(self, iq):
366
- """
367
- Process a request to finish the execution of command
368
- and terminate the workflow.
369
-
370
- All data related to the command session will be removed.
371
-
372
- Arguments:
373
- :param iq: The command completion request.
374
- """
375
- node = iq['command']['node']
376
- sessionid = iq['command']['sessionid']
377
- session = self.sessions.get(sessionid)
378
-
379
- if session:
380
- handler = session['next']
381
- interfaces = session['interfaces']
382
- results = []
383
- for stanza in iq['command']['substanzas']:
384
- if stanza.plugin_attrib in interfaces:
385
- results.append(stanza)
386
- if len(results) == 1:
387
- results = results[0]
388
-
389
- if handler:
390
- await _await_if_needed(handler, results, session)
391
-
392
- del self.sessions[sessionid]
393
-
394
- payload = session['payload']
395
- if payload is None:
396
- payload = []
397
- if not isinstance(payload, list):
398
- payload = [payload]
399
-
400
- for item in payload:
401
- register_stanza_plugin(Command, item.__class__, iterable=True)
402
-
403
- iq = iq.reply()
404
-
405
- iq['command']['node'] = node
406
- iq['command']['sessionid'] = sessionid
407
- iq['command']['actions'] = []
408
- iq['command']['status'] = 'completed'
409
- iq['command']['notes'] = session['notes']
410
-
411
- for item in payload:
412
- iq['command'].append(item)
413
-
414
- iq.send()
415
- else:
416
- raise XMPPError('item-not-found')
417
-
418
- # =================================================================
419
- # Client side (command user) API
420
-
421
- def get_commands(self, jid, **kwargs):
422
- """
423
- Return a list of commands provided by a given JID.
424
-
425
- :param jid: The JID to query for commands.
426
- :param local: If true, then the query is for a JID/node
427
- combination handled by this Slixmpp instance and
428
- no stanzas need to be sent.
429
- Otherwise, a disco stanza must be sent to the
430
- remove JID to retrieve the items.
431
- :param iterator: If True, return a result set iterator using
432
- the XEP-0059 plugin, if the plugin is loaded.
433
- Otherwise the parameter is ignored.
434
- """
435
- return self.xmpp['xep_0030'].get_items(jid=jid,
436
- node=Command.namespace,
437
- **kwargs)
438
-
439
- def send_command(self, jid, node, ifrom=None, action='execute',
440
- payload=None, sessionid=None, flow=False, **kwargs):
441
- """
442
- Create and send a command stanza, without using the provided
443
- workflow management APIs.
444
-
445
- :param jid: The JID to send the command request or result.
446
- :param node: The node for the command.
447
- :param ifrom: Specify the sender's JID.
448
- :param action: May be one of: execute, cancel, complete,
449
- or cancel.
450
- :param payload: Either a list of payload items, or a single
451
- payload item such as a data form.
452
- :param sessionid: The current session's ID value.
453
- :param flow: If True, process the Iq result using the
454
- command workflow methods contained in the
455
- session instead of returning the response
456
- stanza itself. Defaults to False.
457
- """
458
- iq = self.xmpp.Iq()
459
- iq['type'] = 'set'
460
- iq['to'] = jid
461
- iq['from'] = ifrom
462
- iq['command']['node'] = node
463
- iq['command']['action'] = action
464
- if sessionid is not None:
465
- iq['command']['sessionid'] = sessionid
466
- if payload is not None:
467
- if not isinstance(payload, list):
468
- payload = [payload]
469
- for item in payload:
470
- iq['command'].append(item)
471
- if not flow:
472
- return iq.send(**kwargs)
473
- else:
474
- iq.send(callback=self._handle_command_result)
475
-
476
- def start_command(self, jid, node, session, ifrom=None):
477
- """
478
- Initiate executing a command provided by a remote agent.
479
-
480
- The provided session dictionary should contain:
481
-
482
- :param next: A handler for processing the command result.
483
- :param error: A handler for processing any error stanzas
484
- generated by the request.
485
-
486
- :param jid: The JID to send the command request.
487
- :param node: The node for the desired command.
488
- :param session: A dictionary of relevant session data.
489
- """
490
- session['jid'] = jid
491
- session['node'] = node
492
- session['timestamp'] = time.time()
493
- if 'payload' not in session:
494
- session['payload'] = None
495
-
496
- iq = self.xmpp.Iq()
497
- iq['type'] = 'set'
498
- iq['to'] = jid
499
- iq['from'] = ifrom
500
- session['from'] = ifrom
501
- iq['command']['node'] = node
502
- iq['command']['action'] = 'execute'
503
- if session['payload'] is not None:
504
- payload = session['payload']
505
- if not isinstance(payload, list):
506
- payload = list(payload)
507
- for stanza in payload:
508
- iq['command'].append(stanza)
509
- sessionid = 'client:pending_' + iq['id']
510
- session['id'] = sessionid
511
- self.sessions[sessionid] = session
512
- iq.send(callback=self._handle_command_result)
513
-
514
- def continue_command(self, session, direction='next'):
515
- """
516
- Execute the next action of the command.
517
-
518
- :param session: All stored data relevant to the current
519
- command session.
520
- """
521
- sessionid = 'client:' + session['id']
522
- self.sessions[sessionid] = session
523
-
524
- self.send_command(session['jid'],
525
- session['node'],
526
- ifrom=session.get('from', None),
527
- action=direction,
528
- payload=session.get('payload', None),
529
- sessionid=session['id'],
530
- flow=True)
531
-
532
- def cancel_command(self, session):
533
- """
534
- Cancel the execution of a command.
535
-
536
- :param session: All stored data relevant to the current
537
- command session.
538
- """
539
- sessionid = 'client:' + session['id']
540
- self.sessions[sessionid] = session
541
-
542
- self.send_command(session['jid'],
543
- session['node'],
544
- ifrom=session.get('from', None),
545
- action='cancel',
546
- payload=session.get('payload', None),
547
- sessionid=session['id'],
548
- flow=True)
549
-
550
- def complete_command(self, session):
551
- """
552
- Finish the execution of a command workflow.
553
-
554
- :param session: All stored data relevant to the current
555
- command session.
556
- """
557
- sessionid = 'client:' + session['id']
558
- self.sessions[sessionid] = session
559
-
560
- self.send_command(session['jid'],
561
- session['node'],
562
- ifrom=session.get('from', None),
563
- action='complete',
564
- payload=session.get('payload', None),
565
- sessionid=session['id'],
566
- flow=True)
567
-
568
- def terminate_command(self, session):
569
- """
570
- Delete a command's session after a command has completed
571
- or an error has occurred.
572
-
573
- :param session: All stored data relevant to the current
574
- command session.
575
- """
576
- sessionid = 'client:' + session['id']
577
- try:
578
- del self.sessions[sessionid]
579
- except Exception as e:
580
- log.error("Error deleting adhoc command session: %s" % e.message)
581
-
582
- def _handle_command_result(self, iq):
583
- """
584
- Process the results of a command request.
585
-
586
- Will execute the 'next' handler stored in the session
587
- data, or the 'error' handler depending on the Iq's type.
588
-
589
- :param iq: The command response.
590
- """
591
- sessionid = 'client:' + iq['command']['sessionid']
592
- pending = False
593
-
594
- if sessionid not in self.sessions:
595
- pending = True
596
- pendingid = 'client:pending_' + iq['id']
597
- if pendingid not in self.sessions:
598
- return
599
- sessionid = pendingid
600
-
601
- session = self.sessions[sessionid]
602
- sessionid = 'client:' + iq['command']['sessionid']
603
- session['id'] = iq['command']['sessionid']
604
-
605
- self.sessions[sessionid] = session
606
-
607
- if pending:
608
- del self.sessions[pendingid]
609
-
610
- handler_type = 'next'
611
- if iq['type'] == 'error':
612
- handler_type = 'error'
613
- handler = session.get(handler_type, None)
614
- if handler:
615
- handler(iq, session)
616
- elif iq['type'] == 'error':
617
- self.terminate_command(session)
618
-
619
- if iq['command']['status'] == 'completed':
620
- self.terminate_command(session)
621
-
622
-
623
- async def _await_if_needed(handler, *args):
624
- if handler is None:
625
- raise XMPPError("bad-request", text="The command is completed")
626
- if asyncio.iscoroutinefunction(handler):
627
- log.debug(f"%s is async", handler)
628
- return await handler(*args)
629
- else:
630
- log.debug(f"%s is sync", handler)
631
- return handler(*args)