Mercurial > libervia-backend
comparison src/tmp/wokkel/mam.py @ 1774:0c21dafedd22
tmp (wokkel.mam): cleaning and bug fix:
- renamed MAMQueryRequest to MAMQuery
- use of MAMQuery in queryArchive instead of separate arguments
- fixed bad use of subprotocols.IQHandlerMixing.handleRequest
- changed order of arguments in BuildForm to have more often used first
- renamed parse to fromElement for coherency
- added IMAMService interface
- MAMService requestClass can be changed (it is specified in _request_class class attribute
- use new-style classes where it make sense
- MAMRequest.fromElement parse a <query/> parent instead of a <query/> directly
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 05 Jan 2016 23:20:22 +0100 |
parents | f525c272fd6d |
children | 21f0bcd3b637 |
comparison
equal
deleted
inserted
replaced
1773:6e867caf4621 | 1774:0c21dafedd22 |
---|---|
23 | 23 |
24 This protocol is specified in | 24 This protocol is specified in |
25 U{XEP-0313<http://xmpp.org/extensions/xep-0313.html>}. | 25 U{XEP-0313<http://xmpp.org/extensions/xep-0313.html>}. |
26 """ | 26 """ |
27 | 27 |
28 from dateutil.tz import tzutc | 28 from dateutil import tz |
29 | 29 |
30 from zope.interface import Interface, implements | 30 from zope.interface import implements |
31 | 31 from zope.interface import Interface |
32 from twisted.words.protocols.jabber.xmlstream import IQ, toResponse | 32 |
33 from twisted.words.protocols.jabber import xmlstream | |
33 from twisted.words.xish import domish | 34 from twisted.words.xish import domish |
34 from twisted.words.protocols.jabber import jid | 35 from twisted.words.protocols.jabber import jid |
35 from twisted.internet import defer | 36 from twisted.internet import defer |
36 from twisted.python import log | 37 from twisted.python import log |
37 | 38 |
38 from wokkel.subprotocols import IQHandlerMixin, XMPPHandler | 39 from wokkel import subprotocols |
39 from wokkel import disco, data_form, delay | 40 from wokkel import disco |
41 from wokkel import data_form | |
42 from wokkel import delay | |
40 | 43 |
41 import rsm | 44 import rsm |
42 | 45 |
43 NS_MAM = 'urn:xmpp:mam:1' | 46 NS_MAM = 'urn:xmpp:mam:1' |
44 NS_FORWARD = 'urn:xmpp:forward:0' | 47 NS_FORWARD = 'urn:xmpp:forward:0' |
48 PREFS_GET_REQUEST = "/iq[@type='get']/prefs[@xmlns='%s']" % NS_MAM | 51 PREFS_GET_REQUEST = "/iq[@type='get']/prefs[@xmlns='%s']" % NS_MAM |
49 PREFS_SET_REQUEST = "/iq[@type='set']/prefs[@xmlns='%s']" % NS_MAM | 52 PREFS_SET_REQUEST = "/iq[@type='set']/prefs[@xmlns='%s']" % NS_MAM |
50 | 53 |
51 # TODO: add the tests! | 54 # TODO: add the tests! |
52 | 55 |
53 | |
54 class MAMError(Exception): | 56 class MAMError(Exception): |
55 """ | 57 """ |
56 MAM error. | 58 MAM error. |
57 """ | 59 """ |
58 | |
59 | 60 |
60 class Unsupported(MAMError): | 61 class Unsupported(MAMError): |
61 def __init__(self, feature, text=None): | 62 def __init__(self, feature, text=None): |
62 self.feature = feature | 63 self.feature = feature |
63 MAMError.__init__(self, 'feature-not-implemented', | 64 MAMError.__init__(self, 'feature-not-implemented', |
69 message = MAMError.__str__(self) | 70 message = MAMError.__str__(self) |
70 message += ', feature %r' % self.feature | 71 message += ', feature %r' % self.feature |
71 return message | 72 return message |
72 | 73 |
73 | 74 |
74 class MAMQueryRequest(): | 75 class MAMRequest(object): |
75 """ | 76 """ |
76 A Message Archive Management <query/> request. | 77 A Message Archive Management <query/> request. |
77 | 78 |
78 @ivar form: Data Form specifing the filters. | 79 @ivar form: Data Form specifing the filters. |
79 @itype form: L{data_form.Form} | 80 @itype form: L{data_form.Form} |
96 self.rsm = rsm_ | 97 self.rsm = rsm_ |
97 self.node = node | 98 self.node = node |
98 self.query_id = query_id | 99 self.query_id = query_id |
99 | 100 |
100 @classmethod | 101 @classmethod |
101 def parse(cls, element): | 102 def fromElement(cls, element): |
102 """Parse the DOM representation of a MAM <query/> request. | 103 """Parse the DOM representation of a MAM <query/> request. |
103 | 104 |
104 @param element: MAM <query/> request element. | 105 @param element: element containing a MAM <query/>. |
105 @type element: L{Element<twisted.words.xish.domish.Element>} | 106 @type element: L{Element<twisted.words.xish.domish.Element>} |
106 | 107 |
107 @return: MAMQueryRequest instance. | 108 @return: MAMRequest instance. |
108 @rtype: L{MAMQueryRequest} | 109 @rtype: L{MAMRequest} |
109 """ | 110 """ |
110 if element.uri != NS_MAM or element.name != 'query': | |
111 raise MAMError('Element provided is not a MAM <query/> request') | |
112 form = data_form.findForm(element, NS_MAM) | |
113 try: | 111 try: |
114 rsm_request = rsm.RSMRequest.parse(element) | 112 query = element.elements(NS_MAM, 'query').next() |
113 except StopIteration: | |
114 raise MAMError("Can't find MAM <query/> in element") | |
115 form = data_form.findForm(query, NS_MAM) | |
116 try: | |
117 rsm_request = rsm.RSMRequest.fromElement(query) | |
115 except rsm.RSMNotFoundError: | 118 except rsm.RSMNotFoundError: |
116 rsm_request = None | 119 rsm_request = None |
117 node = element.getAttribute('node') | 120 node = query.getAttribute('node') |
118 query_id = element.getAttribute('queryid') | 121 query_id = query.getAttribute('queryid') |
119 return MAMQueryRequest(form, rsm_request, node, query_id) | 122 return MAMRequest(form, rsm_request, node, query_id) |
120 | 123 |
121 def toElement(self): | 124 def toElement(self): |
122 """ | 125 """ |
123 Return the DOM representation of this RSM <query/> request. | 126 Return the DOM representation of this RSM <query/> request. |
124 | 127 |
149 mam_elt = self.toElement() | 152 mam_elt = self.toElement() |
150 parent.addChild(mam_elt) | 153 parent.addChild(mam_elt) |
151 return mam_elt | 154 return mam_elt |
152 | 155 |
153 | 156 |
154 class MAMPrefs(): | 157 class MAMPrefs(object): |
155 """ | 158 """ |
156 A Message Archive Management <prefs/> request. | 159 A Message Archive Management <prefs/> request. |
157 | 160 |
158 @param default: A value in ('always', 'never', 'roster'). | 161 @param default: A value in ('always', 'never', 'roster'). |
159 @type : C{unicode} or C{None} | 162 @type : C{unicode} or C{None} |
178 else: | 181 else: |
179 never = [] | 182 never = [] |
180 self.never = never | 183 self.never = never |
181 | 184 |
182 @classmethod | 185 @classmethod |
183 def parse(cls, prefs_elt): | 186 def fromElement(cls, prefs_elt): |
184 """Parse the DOM representation of a MAM <prefs/> request. | 187 """Parse the DOM representation of a MAM <prefs/> request. |
185 | 188 |
186 @param prefs_elt: MAM <prefs/> request element. | 189 @param prefs_elt: MAM <prefs/> request element. |
187 @type prefs_elt: L{Element<twisted.words.xish.domish.Element>} | 190 @type prefs_elt: L{Element<twisted.words.xish.domish.Element>} |
188 | 191 |
239 mam_elt = self.toElement() | 242 mam_elt = self.toElement() |
240 parent.addChild(mam_elt) | 243 parent.addChild(mam_elt) |
241 return mam_elt | 244 return mam_elt |
242 | 245 |
243 | 246 |
244 class MAMClient(XMPPHandler): | 247 class MAMClient(subprotocols.XMPPHandler): |
245 """ | 248 """ |
246 MAM client. | 249 MAM client. |
247 | 250 |
248 This handler implements the protocol for sending out MAM requests. | 251 This handler implements the protocol for sending out MAM requests. |
249 """ | 252 """ |
250 | 253 |
251 def queryArchive(self, service=None, form=None, rsm=None, node=None, sender=None, query_id=None): | 254 def queryArchive(self, mam_query, service=None, sender=None): |
252 """Query a user, MUC or pubsub archive. | 255 """Query a user, MUC or pubsub archive. |
253 | 256 |
254 @param service: Entity offering the MAM service (None for user archives). | 257 @param mam_query: query to use |
258 @type form: L{MAMRequest} | |
259 | |
260 @param service: Entity offering the MAM service (None for user server). | |
255 @type service: L{JID<twisted.words.protocols.jabber.jid.JID>} | 261 @type service: L{JID<twisted.words.protocols.jabber.jid.JID>} |
256 | |
257 @param form: Data Form to filter the request. | |
258 @type form: L{Form<wokkel.data_form.Form>} | |
259 | |
260 @param rsm: RSM request instance. | |
261 @type rsm: L{RSMRequest<wokkel.rsm.RSMRequest>} | |
262 | |
263 @param node: Pubsub node to query, or None if inappropriate. | |
264 @type node: C{unicode} | |
265 | 262 |
266 @param sender: Optional sender address. | 263 @param sender: Optional sender address. |
267 @type sender: L{JID<twisted.words.protocols.jabber.jid.JID>} | 264 @type sender: L{JID<twisted.words.protocols.jabber.jid.JID>} |
268 | 265 |
269 @param query_id: Optional query id | |
270 @type query_id: C{unicode} | |
271 | |
272 @return: A deferred that fires upon receiving a response. | 266 @return: A deferred that fires upon receiving a response. |
273 @rtype: L{Deferred<twisted.internet.defer.Deferred>} | 267 @rtype: L{Deferred<twisted.internet.defer.Deferred>} |
274 """ | 268 """ |
275 iq = IQ(self.xmlstream, 'set') | 269 iq = xmlstream.IQ(self.xmlstream, 'set') |
276 MAMQueryRequest(form, rsm, node, query_id).render(iq) | 270 mam_query.render(iq) |
277 if sender is not None: | 271 if sender is not None: |
278 iq['from'] = unicode(sender) | 272 iq['from'] = unicode(sender) |
279 return iq.send(to=service.full() if service else None) | 273 return iq.send(to=service.full() if service else None) |
280 | 274 |
281 def queryFields(self, service=None, sender=None): | 275 def queryFields(self, service=None, sender=None): |
282 """Ask the server about additional supported fields. | 276 """Ask the server about supported fields. |
283 | 277 |
284 @param service: Entity offering the MAM service (None for user archives). | 278 @param service: Entity offering the MAM service (None for user archives). |
285 @type service: L{JID<twisted.words.protocols.jabber.jid.JID>} | 279 @type service: L{JID<twisted.words.protocols.jabber.jid.JID>} |
286 | 280 |
287 @param sender: Optional sender address. | 281 @param sender: Optional sender address. |
289 | 283 |
290 @return: data Form with the fields, or None if not found | 284 @return: data Form with the fields, or None if not found |
291 @rtype: L{Deferred<twisted.internet.defer.Deferred>} | 285 @rtype: L{Deferred<twisted.internet.defer.Deferred>} |
292 """ | 286 """ |
293 # http://xmpp.org/extensions/xep-0313.html#query-form | 287 # http://xmpp.org/extensions/xep-0313.html#query-form |
294 iq = IQ(self.xmlstream, 'get') | 288 iq = xmlstream.IQ(self.xmlstream, 'get') |
295 MAMQueryRequest().render(iq) | 289 MAMRequest().render(iq) |
296 if sender is not None: | 290 if sender is not None: |
297 iq['from'] = unicode(sender) | 291 iq['from'] = unicode(sender) |
298 d = iq.send(to=service.full() if service else None) | 292 d = iq.send(to=service.full() if service else None) |
299 d.addCallback(lambda iq_result: iq_result.elements(NS_MAM, 'query').next()) | 293 d.addCallback(lambda iq_result: iq_result.elements(NS_MAM, 'query').next()) |
300 d.addCallback(data_form.findForm, NS_MAM) | 294 d.addCallback(data_form.findForm, NS_MAM) |
311 | 305 |
312 @return: A deferred that fires upon receiving a response. | 306 @return: A deferred that fires upon receiving a response. |
313 @rtype: L{Deferred<twisted.internet.defer.Deferred>} | 307 @rtype: L{Deferred<twisted.internet.defer.Deferred>} |
314 """ | 308 """ |
315 # http://xmpp.org/extensions/xep-0313.html#prefs | 309 # http://xmpp.org/extensions/xep-0313.html#prefs |
316 iq = IQ(self.xmlstream, 'get') | 310 iq = xmlstream.IQ(self.xmlstream, 'get') |
317 MAMPrefs().render(iq) | 311 MAMPrefs().render(iq) |
318 if sender is not None: | 312 if sender is not None: |
319 iq['from'] = unicode(sender) | 313 iq['from'] = unicode(sender) |
320 return iq.send(to=service.full() if service else None) | 314 return iq.send(to=service.full() if service else None) |
321 | 315 |
340 @return: A deferred that fires upon receiving a response. | 334 @return: A deferred that fires upon receiving a response. |
341 @rtype: L{Deferred<twisted.internet.defer.Deferred>} | 335 @rtype: L{Deferred<twisted.internet.defer.Deferred>} |
342 """ | 336 """ |
343 # http://xmpp.org/extensions/xep-0313.html#prefs | 337 # http://xmpp.org/extensions/xep-0313.html#prefs |
344 assert default is not None | 338 assert default is not None |
345 iq = IQ(self.xmlstream, 'set') | 339 iq = xmlstream.IQ(self.xmlstream, 'set') |
346 MAMPrefs(default, always, never).render(iq) | 340 MAMPrefs(default, always, never).render(iq) |
347 if sender is not None: | 341 if sender is not None: |
348 iq['from'] = unicode(sender) | 342 iq['from'] = unicode(sender) |
349 return iq.send(to=service.full() if service else None) | 343 return iq.send(to=service.full() if service else None) |
350 | 344 |
353 | 347 |
354 def onArchiveRequest(self, mam, requestor): | 348 def onArchiveRequest(self, mam, requestor): |
355 """ | 349 """ |
356 | 350 |
357 @param mam: The MAM <query/> request. | 351 @param mam: The MAM <query/> request. |
358 @type mam: L{MAMQueryReques<wokkel.mam.MAMQueryRequest>} | 352 @type mam: L{MAMQueryReques<wokkel.mam.MAMRequest>} |
359 | 353 |
360 @param requestor: JID of the requestor. | 354 @param requestor: JID of the requestor. |
361 @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>} | 355 @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>} |
362 | 356 |
363 @return: The RSM answer. | 357 @return: The RSM answer. |
385 | 379 |
386 @return: The new current settings. | 380 @return: The new current settings. |
387 @rtype: L{wokkel.mam.MAMPrefs} | 381 @rtype: L{wokkel.mam.MAMPrefs} |
388 """ | 382 """ |
389 | 383 |
390 | 384 class IMAMService(Interface): |
391 class MAMService(XMPPHandler, IQHandlerMixin): | 385 """ |
386 Interface for XMPP MAM service. | |
387 """ | |
388 | |
389 def addFilter(self, field): | |
390 """ | |
391 Add a new filter for querying MAM archive. | |
392 | |
393 @param field: data form field of the filter | |
394 @type field: L{Form<wokkel.data_form.Field>} | |
395 """ | |
396 | |
397 | |
398 class MAMService(subprotocols.XMPPHandler, subprotocols.IQHandlerMixin): | |
392 """ | 399 """ |
393 Protocol implementation for a MAM service. | 400 Protocol implementation for a MAM service. |
394 | 401 |
395 This handler waits for XMPP Ping requests and sends a response. | 402 This handler waits for XMPP Ping requests and sends a response. |
396 """ | 403 """ |
397 | 404 implements(IMAMService, disco.IDisco) |
398 implements(disco.IDisco) | 405 |
406 _request_class = MAMRequest | |
399 | 407 |
400 iqHandlers = {FIELDS_REQUEST: '_onFieldsRequest', | 408 iqHandlers = {FIELDS_REQUEST: '_onFieldsRequest', |
401 ARCHIVE_REQUEST: '_onArchiveRequest', | 409 ARCHIVE_REQUEST: '_onArchiveRequest', |
402 PREFS_GET_REQUEST: '_onPrefsGetRequest', | 410 PREFS_GET_REQUEST: '_onPrefsGetRequest', |
403 PREFS_SET_REQUEST: '_onPrefsSetRequest' | 411 PREFS_SET_REQUEST: '_onPrefsSetRequest' |
452 """ | 460 """ |
453 Called when a fields request has been received. | 461 Called when a fields request has been received. |
454 | 462 |
455 This immediately replies with a result response. | 463 This immediately replies with a result response. |
456 """ | 464 """ |
457 response = toResponse(iq, 'result') | |
458 query = response.addElement((NS_MAM, 'query')) | |
459 query.addChild(buildForm('form', extra=self.extra_fields).toElement()) | |
460 self.xmlstream.send(response) | |
461 iq.handled = True | 465 iq.handled = True |
466 query = domish.Element((NS_MAM, 'query')) | |
467 query.addChild(buildForm(extra_fields=self.extra_fields).toElement(), formType='form') | |
468 return query | |
462 | 469 |
463 def _onArchiveRequest(self, iq): | 470 def _onArchiveRequest(self, iq): |
464 """ | 471 """ |
465 Called when a message archive request has been received. | 472 Called when a message archive request has been received. |
466 | 473 |
467 This replies with the list of archived message and the <iq> result | 474 This replies with the list of archived message and the <iq> result |
468 @return: A tuple with list of message data (id, element, data) and RSM element | 475 @return: A tuple with list of message data (id, element, data) and RSM element |
469 @rtype: C{tuple} | 476 @rtype: C{tuple} |
470 """ | 477 """ |
471 mam_ = MAMQueryRequest.parse(iq.query) | 478 iq.handled = True |
479 mam_ = self._request_class.fromElement(iq) | |
472 requestor = jid.JID(iq['from']) | 480 requestor = jid.JID(iq['from']) |
473 | 481 |
474 # remove unsupported filters | 482 # remove unsupported filters |
475 unsupported_fields = [] | 483 unsupported_fields = [] |
476 if mam_.form: | 484 if mam_.form: |
479 log.msg('Ignored unsupported MAM filter: %s' % field) | 487 log.msg('Ignored unsupported MAM filter: %s' % field) |
480 unsupported_fields.append(key) | 488 unsupported_fields.append(key) |
481 for key in unsupported_fields: | 489 for key in unsupported_fields: |
482 del mam_.form.fields[key] | 490 del mam_.form.fields[key] |
483 | 491 |
484 def forward_message(id_, elt, date): | 492 def forwardMessage(id_, elt, date): |
485 msg = domish.Element((None, 'message')) | 493 msg = domish.Element((None, 'message')) |
486 msg['to'] = iq['from'] | 494 msg['to'] = iq['from'] |
487 result = msg.addElement((NS_MAM, 'result')) | 495 result = msg.addElement((NS_MAM, 'result')) |
488 try: | 496 if mam_.query_id is not None: |
489 result['queryid'] = iq.query['queryid'] | 497 result['queryid'] = mam_.query_id |
490 except KeyError: | |
491 pass | |
492 result['id'] = id_ | 498 result['id'] = id_ |
493 forward = result.addElement((NS_FORWARD, 'forwarded')) | 499 forward = result.addElement((NS_FORWARD, 'forwarded')) |
494 forward.addChild(delay.Delay(date).toElement()) | 500 forward.addChild(delay.Delay(date).toElement()) |
495 forward.addChild(elt) | 501 forward.addChild(elt) |
496 self.xmlstream.send(msg) | 502 self.xmlstream.send(msg) |
497 | 503 |
498 def cb(result): | 504 def cb(result): |
499 msg_data, rsm_elt = result | 505 msg_data, rsm_elt = result |
500 for data in msg_data: | 506 for data in msg_data: |
501 forward_message(*data) | 507 forwardMessage(*data) |
502 | 508 |
503 response = toResponse(iq, 'result') | 509 fin_elt = domish.Element((NS_MAM, 'fin')) |
504 fin = response.addElement((NS_MAM, 'fin')) | |
505 | 510 |
506 if rsm_elt is not None: | 511 if rsm_elt is not None: |
507 fin.addChild(rsm_elt) | 512 fin_elt.addChild(rsm_elt) |
508 self.xmlstream.send(response) | 513 return fin_elt |
509 | 514 |
510 d = defer.maybeDeferred(self.resource.onArchiveRequest, mam_, requestor) | 515 d = defer.maybeDeferred(self.resource.onArchiveRequest, mam_, requestor) |
511 d.addCallback(cb) | 516 d.addCallback(cb) |
517 return d | |
518 | |
519 def _onPrefsGetRequest(self, iq): | |
520 """ | |
521 Called when a prefs get request has been received. | |
522 | |
523 This immediately replies with a result response. | |
524 """ | |
512 iq.handled = True | 525 iq.handled = True |
513 | 526 requestor = jid.JID(iq['from']) |
514 def _onPrefsGetRequest(self, iq): | 527 |
528 def cb(prefs): | |
529 return prefs.toElement() | |
530 | |
531 d = self.resource.onPrefsGetRequest(requestor).addCallback(cb) | |
532 return d | |
533 | |
534 def _onPrefsSetRequest(self, iq): | |
515 """ | 535 """ |
516 Called when a prefs get request has been received. | 536 Called when a prefs get request has been received. |
517 | 537 |
518 This immediately replies with a result response. | 538 This immediately replies with a result response. |
519 """ | 539 """ |
520 response = toResponse(iq, 'result') | 540 iq.handled = True |
521 | 541 |
542 prefs = MAMPrefs.fromElement(iq.prefs) | |
522 requestor = jid.JID(iq['from']) | 543 requestor = jid.JID(iq['from']) |
523 | 544 |
524 def cb(prefs): | 545 def cb(prefs): |
525 response.addChild(prefs.toElement()) | 546 return prefs.toElement() |
526 self.xmlstream.send(response) | 547 |
527 | 548 d = self.resource.onPrefsSetRequest(prefs, requestor).addCallback(cb) |
528 self.resource.onPrefsGetRequest(requestor).addCallback(cb) | 549 return d |
529 iq.handled = True | |
530 | |
531 def _onPrefsSetRequest(self, iq): | |
532 """ | |
533 Called when a prefs get request has been received. | |
534 | |
535 This immediately replies with a result response. | |
536 """ | |
537 response = toResponse(iq, 'result') | |
538 | |
539 prefs = MAMPrefs.parse(iq.prefs) | |
540 requestor = jid.JID(iq['from']) | |
541 | |
542 def cb(prefs): | |
543 response.addChild(prefs.toElement()) | |
544 self.xmlstream.send(response) | |
545 | |
546 self.resource.onPrefsSetRequest(prefs, requestor).addCallback(cb) | |
547 iq.handled = True | |
548 | 550 |
549 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | 551 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): |
550 if nodeIdentifier: | 552 if nodeIdentifier: |
551 return [] | 553 return [] |
552 return [disco.DiscoFeature(NS_MAM)] | 554 return [disco.DiscoFeature(NS_MAM)] |
553 | 555 |
554 def getDiscoItems(self, requestor, target, nodeIdentifier=''): | 556 def getDiscoItems(self, requestor, target, nodeIdentifier=''): |
555 return [] | 557 return [] |
556 | 558 |
557 | 559 |
558 def datetime2utc(datetime): | 560 def datetime2utc(datetime_obj): |
559 """Convert a datetime to a XEP-0082 compliant UTC datetime. | 561 """Convert a datetime to a XEP-0082 compliant UTC datetime. |
560 | 562 |
561 @param datetime: Offset-aware timestamp to convert. | 563 @param datetime_obj: Offset-aware timestamp to convert. |
562 @type datetime: L{datetime<datetime.datetime>} | 564 @type datetime_obj: L{datetime<datetime.datetime>} |
563 | 565 |
564 @return: The datetime converted to UTC. | 566 @return: The datetime converted to UTC. |
565 @rtype: C{unicode} | 567 @rtype: C{unicode} |
566 """ | 568 """ |
567 stampFormat = '%Y-%m-%dT%H:%M:%SZ' | 569 stampFormat = '%Y-%m-%dT%H:%M:%SZ' |
568 return datetime.astimezone(tzutc()).strftime(stampFormat) | 570 return datetime_obj.astimezone(tz.tzutc()).strftime(stampFormat) |
569 | 571 |
570 | 572 |
571 def buildForm(formType='submit', start=None, end=None, with_jid=None, extra_fields=None): | 573 def buildForm(start=None, end=None, with_jid=None, extra_fields=None, formType='submit'): |
572 """Prepare a Data Form for MAM. | 574 """Prepare a Data Form for MAM. |
573 | |
574 @param formType: The type of the Data Form ('submit' or 'form'). | |
575 @type formType: C{unicode} | |
576 | 575 |
577 @param start: Offset-aware timestamp to filter out older messages. | 576 @param start: Offset-aware timestamp to filter out older messages. |
578 @type start: L{datetime<datetime.datetime>} | 577 @type start: L{datetime<datetime.datetime>} |
579 | 578 |
580 @param end: Offset-aware timestamp to filter out later messages. | 579 @param end: Offset-aware timestamp to filter out later messages. |
584 @type with_jid: L{JID<twisted.words.protocols.jabber.jid.JID>} | 583 @type with_jid: L{JID<twisted.words.protocols.jabber.jid.JID>} |
585 | 584 |
586 @param extra_fields: list of extra data form fields that are not defined by the | 585 @param extra_fields: list of extra data form fields that are not defined by the |
587 specification. | 586 specification. |
588 @type: C{list} | 587 @type: C{list} |
588 | |
589 @param formType: The type of the Data Form ('submit' or 'form'). | |
590 @type formType: C{unicode} | |
589 | 591 |
590 @return: XEP-0004 Data Form object. | 592 @return: XEP-0004 Data Form object. |
591 @rtype: L{Form<wokkel.data_form.Form>} | 593 @rtype: L{Form<wokkel.data_form.Form>} |
592 """ | 594 """ |
593 form = data_form.Form(formType, formNamespace=NS_MAM) | 595 form = data_form.Form(formType, formNamespace=NS_MAM) |