1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Protocol module contains tools that is needed for processing of
19 xmpp-related data structures.
20 """
21
22 from simplexml import Node,ustr
23 import time
24 NS_ACTIVITY ='http://jabber.org/protocol/activity'
25 NS_ADDRESS ='http://jabber.org/protocol/address'
26 NS_ADMIN ='http://jabber.org/protocol/admin'
27 NS_ADMIN_ADD_USER =NS_ADMIN+'#add-user'
28 NS_ADMIN_DELETE_USER =NS_ADMIN+'#delete-user'
29 NS_ADMIN_DISABLE_USER =NS_ADMIN+'#disable-user'
30 NS_ADMIN_REENABLE_USER =NS_ADMIN+'#reenable-user'
31 NS_ADMIN_END_USER_SESSION =NS_ADMIN+'#end-user-session'
32 NS_ADMIN_GET_USER_PASSWORD =NS_ADMIN+'#get-user-password'
33 NS_ADMIN_CHANGE_USER_PASSWORD =NS_ADMIN+'#change-user-password'
34 NS_ADMIN_GET_USER_ROSTER =NS_ADMIN+'#get-user-roster'
35 NS_ADMIN_GET_USER_LASTLOGIN =NS_ADMIN+'#get-user-lastlogin'
36 NS_ADMIN_USER_STATS =NS_ADMIN+'#user-stats'
37 NS_ADMIN_EDIT_BLACKLIST =NS_ADMIN+'#edit-blacklist'
38 NS_ADMIN_EDIT_WHITELIST =NS_ADMIN+'#edit-whitelist'
39 NS_ADMIN_REGISTERED_USERS_NUM =NS_ADMIN+'#get-registered-users-num'
40 NS_ADMIN_DISABLED_USERS_NUM =NS_ADMIN+'#get-disabled-users-num'
41 NS_ADMIN_ONLINE_USERS_NUM =NS_ADMIN+'#get-online-users-num'
42 NS_ADMIN_ACTIVE_USERS_NUM =NS_ADMIN+'#get-active-users-num'
43 NS_ADMIN_IDLE_USERS_NUM =NS_ADMIN+'#get-idle-users-num'
44 NS_ADMIN_REGISTERED_USERS_LIST =NS_ADMIN+'#get-registered-users-list'
45 NS_ADMIN_DISABLED_USERS_LIST =NS_ADMIN+'#get-disabled-users-list'
46 NS_ADMIN_ONLINE_USERS_LIST =NS_ADMIN+'#get-online-users-list'
47 NS_ADMIN_ACTIVE_USERS_LIST =NS_ADMIN+'#get-active-users-list'
48 NS_ADMIN_IDLE_USERS_LIST =NS_ADMIN+'#get-idle-users-list'
49 NS_ADMIN_ANNOUNCE =NS_ADMIN+'#announce'
50 NS_ADMIN_SET_MOTD =NS_ADMIN+'#set-motd'
51 NS_ADMIN_EDIT_MOTD =NS_ADMIN+'#edit-motd'
52 NS_ADMIN_DELETE_MOTD =NS_ADMIN+'#delete-motd'
53 NS_ADMIN_SET_WELCOME =NS_ADMIN+'#set-welcome'
54 NS_ADMIN_DELETE_WELCOME =NS_ADMIN+'#delete-welcome'
55 NS_ADMIN_EDIT_ADMIN =NS_ADMIN+'#edit-admin'
56 NS_ADMIN_RESTART =NS_ADMIN+'#restart'
57 NS_ADMIN_SHUTDOWN =NS_ADMIN+'#shutdown'
58 NS_AGENTS ='jabber:iq:agents'
59 NS_AMP ='http://jabber.org/protocol/amp'
60 NS_AMP_ERRORS =NS_AMP+'#errors'
61 NS_AUTH ='jabber:iq:auth'
62 NS_AVATAR ='jabber:iq:avatar'
63 NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
64 NS_BROWSE ='jabber:iq:browse'
65 NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams'
66 NS_CAPS ='http://jabber.org/protocol/caps'
67 NS_CHATSTATES ='http://jabber.org/protocol/chatstates'
68 NS_CLIENT ='jabber:client'
69 NS_COMMANDS ='http://jabber.org/protocol/commands'
70 NS_COMPONENT_ACCEPT ='jabber:component:accept'
71 NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0'
72 NS_COMPRESS ='http://jabber.org/protocol/compress'
73 NS_DATA ='jabber:x:data'
74 NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout'
75 NS_DATA_VALIDATE ='http://jabber.org/protocol/xdata-validate'
76 NS_DELAY ='jabber:x:delay'
77 NS_DIALBACK ='jabber:server:dialback'
78 NS_DISCO ='http://jabber.org/protocol/disco'
79 NS_DISCO_INFO =NS_DISCO+'#info'
80 NS_DISCO_ITEMS =NS_DISCO+'#items'
81 NS_ENCRYPTED ='jabber:x:encrypted'
82 NS_EVENT ='jabber:x:event'
83 NS_FEATURE ='http://jabber.org/protocol/feature-neg'
84 NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer'
85 NS_GATEWAY ='jabber:iq:gateway'
86 NS_GEOLOC ='http://jabber.org/protocol/geoloc'
87 NS_GROUPCHAT ='gc-1.0'
88 NS_HTTP_BIND ='http://jabber.org/protocol/httpbind'
89 NS_IBB ='http://jabber.org/protocol/ibb'
90 NS_INVISIBLE ='presence-invisible'
91 NS_IQ ='iq'
92 NS_LAST ='jabber:iq:last'
93 NS_MESSAGE ='message'
94 NS_MOOD ='http://jabber.org/protocol/mood'
95 NS_MUC ='http://jabber.org/protocol/muc'
96 NS_MUC_ADMIN =NS_MUC+'#admin'
97 NS_MUC_OWNER =NS_MUC+'#owner'
98 NS_MUC_UNIQUE =NS_MUC+'#unique'
99 NS_MUC_USER =NS_MUC+'#user'
100 NS_MUC_REGISTER =NS_MUC+'#register'
101 NS_MUC_REQUEST =NS_MUC+'#request'
102 NS_MUC_ROOMCONFIG =NS_MUC+'#roomconfig'
103 NS_MUC_ROOMINFO =NS_MUC+'#roominfo'
104 NS_MUC_ROOMS =NS_MUC+'#rooms'
105 NS_MUC_TRAFIC =NS_MUC+'#traffic'
106 NS_NICK ='http://jabber.org/protocol/nick'
107 NS_OFFLINE ='http://jabber.org/protocol/offline'
108 NS_PHYSLOC ='http://jabber.org/protocol/physloc'
109 NS_PRESENCE ='presence'
110 NS_PRIVACY ='jabber:iq:privacy'
111 NS_PRIVATE ='jabber:iq:private'
112 NS_PUBSUB ='http://jabber.org/protocol/pubsub'
113 NS_REGISTER ='jabber:iq:register'
114 NS_RC ='http://jabber.org/protocol/rc'
115 NS_ROSTER ='jabber:iq:roster'
116 NS_ROSTERX ='http://jabber.org/protocol/rosterx'
117 NS_RPC ='jabber:iq:rpc'
118 NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl'
119 NS_SEARCH ='jabber:iq:search'
120 NS_SERVER ='jabber:server'
121 NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session'
122 NS_SI ='http://jabber.org/protocol/si'
123 NS_SI_PUB ='http://jabber.org/protocol/sipub'
124 NS_SIGNED ='jabber:x:signed'
125 NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
126 NS_STREAMS ='http://etherx.jabber.org/streams'
127 NS_TIME ='jabber:iq:time'
128 NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
129 NS_VACATION ='http://jabber.org/protocol/vacation'
130 NS_VCARD ='vcard-temp'
131 NS_VCARD_UPDATE ='vcard-temp:x:update'
132 NS_VERSION ='jabber:iq:version'
133 NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist'
134 NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im'
135 NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams'
136
137 xmpp_stream_error_conditions="""
138 bad-format -- -- -- The entity has sent XML that cannot be processed.
139 bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.
140 conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.
141 connection-timeout -- -- -- The entity has not generated any traffic over the stream for some period of time.
142 host-gone -- -- -- The value of the 'to' attribute provided by the initiating entity in the stream header corresponds to a hostname that is no longer hosted by the server.
143 host-unknown -- -- -- The value of the 'to' attribute provided by the initiating entity in the stream header does not correspond to a hostname that is hosted by the server.
144 improper-addressing -- -- -- A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value).
145 internal-server-error -- -- -- The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream.
146 invalid-from -- cancel -- -- The JID or hostname provided in a 'from' address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization.
147 invalid-id -- -- -- The stream ID or dialback ID is invalid or does not match an ID previously provided.
148 invalid-namespace -- -- -- The streams namespace name is something other than "http://etherx.jabber.org/streams" or the dialback namespace name is something other than "jabber:server:dialback".
149 invalid-xml -- -- -- The entity has sent invalid XML over the stream to a server that performs validation.
150 not-authorized -- -- -- The entity has attempted to send data before the stream has been authenticated, or otherwise is not authorized to perform an action related to stream negotiation.
151 policy-violation -- -- -- The entity has violated some local service policy.
152 remote-connection-failed -- -- -- The server is unable to properly connect to a remote resource that is required for authentication or authorization.
153 resource-constraint -- -- -- The server lacks the system resources necessary to service the stream.
154 restricted-xml -- -- -- The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character.
155 see-other-host -- -- -- The server will not provide service to the initiating entity but is redirecting traffic to another host.
156 system-shutdown -- -- -- The server is being shut down and all active streams are being closed.
157 undefined-condition -- -- -- The error condition is not one of those defined by the other conditions in this list.
158 unsupported-encoding -- -- -- The initiating entity has encoded the stream in an encoding that is not supported by the server.
159 unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server.
160 unsupported-version -- -- -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server.
161 xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed."""
162 xmpp_stanza_error_conditions="""
163 bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed.
164 conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address.
165 feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed.
166 forbidden -- 403 -- auth -- The requesting entity does not possess the required permissions to perform the action.
167 gone -- 302 -- modify -- The recipient or server can no longer be contacted at this address.
168 internal-server-error -- 500 -- wait -- The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error.
169 item-not-found -- 404 -- cancel -- The addressed JID or item requested cannot be found.
170 jid-malformed -- 400 -- modify -- The value of the 'to' attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme.
171 not-acceptable -- 406 -- cancel -- The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server.
172 not-allowed -- 405 -- cancel -- The recipient or server does not allow any entity to perform the action.
173 not-authorized -- 401 -- auth -- The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials.
174 payment-required -- 402 -- auth -- The requesting entity is not authorized to access the requested service because payment is required.
175 recipient-unavailable -- 404 -- wait -- The intended recipient is temporarily unavailable.
176 redirect -- 302 -- modify -- The recipient or server is redirecting requests for this information to another entity.
177 registration-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because registration is required.
178 remote-server-not-found -- 404 -- cancel -- A remote server or service specified as part or all of the JID of the intended recipient does not exist.
179 remote-server-timeout -- 504 -- wait -- A remote server or service specified as part or all of the JID of the intended recipient could not be contacted within a reasonable amount of time.
180 resource-constraint -- 500 -- wait -- The server or recipient lacks the system resources necessary to service the request.
181 service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service.
182 subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required.
183 undefined-condition -- 500 -- --
184 unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order)."""
185 sasl_error_conditions="""
186 aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.
187 incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data.
188 invalid-authzid -- -- -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data.
189 invalid-mechanism -- -- -- The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element.
190 mechanism-too-weak -- -- -- The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data.
191 not-authorized -- -- -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data.
192 temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element."""
193
194 ERRORS,_errorcodes={},{}
195 for ns,errname,errpool in [(NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions),
196 (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions),
197 (NS_SASL ,'SASL' ,sasl_error_conditions)]:
198 for err in errpool.split('\n')[1:]:
199 cond,code,typ,text=err.split(' -- ')
200 name=errname+'_'+cond.upper().replace('-','_')
201 locals()[name]=ns+' '+cond
202 ERRORS[ns+' '+cond]=[code,typ,text]
203 if code: _errorcodes[code]=cond
204 del ns,errname,errpool,err,cond,code,typ,text
205
207 """ Returns true if the node is a positive reply. """
208 return node and node.getType()=='result'
210 """ Returns true if the node is a negative reply. """
211 return node and node.getType()=='error'
212
214 """ Exception that should be raised by handler when the handling should be stopped. """
216 """ Base exception class for stream errors."""
241
242 stream_exceptions = {'bad-format': BadFormat,
243 'bad-namespace-prefix': BadNamespacePrefix,
244 'conflict': Conflict,
245 'connection-timeout': ConnectionTimeout,
246 'host-gone': HostGone,
247 'host-unknown': HostUnknown,
248 'improper-addressing': ImproperAddressing,
249 'internal-server-error': InternalServerError,
250 'invalid-from': InvalidFrom,
251 'invalid-id': InvalidID,
252 'invalid-namespace': InvalidNamespace,
253 'invalid-xml': InvalidXML,
254 'not-authorized': NotAuthorized,
255 'policy-violation': PolicyViolation,
256 'remote-connection-failed': RemoteConnectionFailed,
257 'resource-constraint': ResourceConstraint,
258 'restricted-xml': RestrictedXML,
259 'see-other-host': SeeOtherHost,
260 'system-shutdown': SystemShutdown,
261 'undefined-condition': UndefinedCondition,
262 'unsupported-encoding': UnsupportedEncoding,
263 'unsupported-stanza-type': UnsupportedStanzaType,
264 'unsupported-version': UnsupportedVersion,
265 'xml-not-well-formed': XMLNotWellFormed}
266
268 """ JID object. JID can be built from string, modified, compared, serialised into string. """
269 - def __init__(self, jid=None, node='', domain='', resource=''):
270 """ Constructor. JID can be specified as string (jid argument) or as separate parts.
271 Examples:
272 JID('node@domain/resource')
273 JID(node='node',domain='domain.org')
274 """
275 if not jid and not domain: raise ValueError('JID must contain at least domain name')
276 elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource
277 elif domain: self.node,self.domain,self.resource=node,domain,resource
278 else:
279 if jid.find('@')+1: self.node,jid=jid.split('@',1)
280 else: self.node=''
281 if jid.find('/')+1: self.domain,self.resource=jid.split('/',1)
282 else: self.domain,self.resource=jid,''
284 """ Return the node part of the JID """
285 return self.node
287 """ Set the node part of the JID to new value. Specify None to remove the node part."""
288 self.node=node.lower()
289 - def getDomain(self):
290 """ Return the domain part of the JID """
291 return self.domain
292 - def setDomain(self,domain):
293 """ Set the domain part of the JID to new value."""
294 self.domain=domain.lower()
296 """ Return the resource part of the JID """
297 return self.resource
299 """ Set the resource part of the JID to new value. Specify None to remove the resource part."""
300 self.resource=resource
302 """ Return the bare representation of JID. I.e. string value w/o resource. """
303 return self.__str__(0)
305 """ Compare the JID to another instance or to string for equality. """
306 try: other=JID(other)
307 except ValueError: return 0
308 return self.resource==other.resource and self.__str__(0) == other.__str__(0)
310 """ Compare the JID to another instance or to string for non-equality. """
311 return not self.__eq__(other)
313 """ Compare the node and domain parts of the JID's for equality. """
314 return self.__str__(0) == JID(other).__str__(0)
316 """ Serialise JID into string. """
317 if self.node: jid=self.node+'@'+self.domain
318 else: jid=self.domain
319 if wresource and self.resource: return jid+'/'+self.resource
320 return jid
322 """ Produce hash of the JID, Allows to use JID objects as keys of the dictionary. """
323 return hash(self.__str__())
324
326 """ A "stanza" object class. Contains methods that are common for presences, iqs and messages. """
327 - def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
328 """ Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'.
329 to is the value of 'to' attribure, 'typ' - 'type' attribute
330 frn - from attribure, attrs - other attributes mapping, payload - same meaning as for simplexml payload definition
331 timestamp - the time value that needs to be stamped over stanza
332 xmlns - namespace of top stanza node
333 node - parsed or unparsed stana to be taken as prototype.
334 """
335 if not attrs: attrs={}
336 if to: attrs['to']=to
337 if frm: attrs['from']=frm
338 if typ: attrs['type']=typ
339 Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
340 if not node and xmlns: self.setNamespace(xmlns)
341 if self['to']: self.setTo(self['to'])
342 if self['from']: self.setFrom(self['from'])
343 if node and type(self)==type(node) and self.__class__==node.__class__ and self.attrs.has_key('id'): del self.attrs['id']
344 self.timestamp=None
345 for x in self.getTags('x',namespace=NS_DELAY):
346 try:
347 if not self.getTimestamp() or x.getAttr('stamp')<self.getTimestamp(): self.setTimestamp(x.getAttr('stamp'))
348 except: pass
349 if timestamp is not None: self.setTimestamp(timestamp)
351 """ Return value of the 'to' attribute. """
352 try: return self['to']
353 except: return None
355 """ Return value of the 'from' attribute. """
356 try: return self['from']
357 except: return None
359 """ Return the timestamp in the 'yyyymmddThhmmss' format. """
360 return self.timestamp
362 """ Return the value of the 'id' attribute. """
363 return self.getAttr('id')
365 """ Set the value of the 'to' attribute. """
366 self.setAttr('to', JID(val))
368 """ Return the value of the 'type' attribute. """
369 return self.getAttr('type')
371 """ Set the value of the 'from' attribute. """
372 self.setAttr('from', JID(val))
374 """ Set the value of the 'type' attribute. """
375 self.setAttr('type', val)
377 """ Set the value of the 'id' attribute. """
378 self.setAttr('id', val)
380 """ Return the error-condition (if present) or the textual description of the error (otherwise). """
381 errtag=self.getTag('error')
382 if errtag:
383 for tag in errtag.getChildren():
384 if tag.getName()<>'text': return tag.getName()
385 return errtag.getData()
387 """ Return the error code. Obsolette. """
388 return self.getTagAttr('error','code')
398 """Set the timestamp. timestamp should be the yyyymmddThhmmss string."""
399 if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
400 self.timestamp=val
401 self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
403 """ Return the list of namespaces to which belongs the direct childs of element"""
404 props=[]
405 for child in self.getChildren():
406 prop=child.getNamespace()
407 if prop not in props: props.append(prop)
408 return props
410 """ Set the item 'item' to the value 'val'."""
411 if item in ['to','from']: val=JID(val)
412 return self.setAttr(item,val)
413
415 """ XMPP Message stanza - "push" mechanism."""
416 - def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
417 """ Create message object. You can specify recipient, text of message, type of message
418 any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
419 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """
420 Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
421 if body: self.setBody(body)
422 if subject: self.setSubject(subject)
424 """ Returns text of the message. """
425 return self.getTagData('body')
427 """ Returns subject of the message. """
428 return self.getTagData('subject')
430 """ Returns thread of the message. """
431 return self.getTagData('thread')
432 - def setBody(self,val):
433 """ Sets the text of the message. """
434 self.setTagData('body',val)
436 """ Sets the subject of the message. """
437 self.setTagData('subject',val)
439 """ Sets the thread of the message. """
440 self.setTagData('thread',val)
442 """ Builds and returns another message object with specified text.
443 The to, from, thread and type properties of new message are pre-set as reply to this message. """
444 m=Message(to=self.getFrom(),frm=self.getTo(),body=text,typ=self.getType())
445 th=self.getThread()
446 if th: m.setThread(th)
447 return m
448
450 """ XMPP Presence object."""
451 - def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
452 """ Create presence object. You can specify recipient, type of message, priority, show and status values
453 any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
454 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. """
455 Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
456 if priority: self.setPriority(priority)
457 if show: self.setShow(show)
458 if status: self.setStatus(status)
460 """ Returns the priority of the message. """
461 return self.getTagData('priority')
463 """ Returns the show value of the message. """
464 return self.getTagData('show')
466 """ Returns the status string of the message. """
467 return self.getTagData('status')
469 """ Sets the priority of the message. """
470 self.setTagData('priority',val)
472 """ Sets the show value of the message. """
473 self.setTagData('show',val)
475 """ Sets the status string of the message. """
476 self.setTagData('status',val)
477
483 for xtag in self.getTags('x'):
484 for child in xtag.getTags('item'):
485 for cchild in child.getTags(tag):
486 return cchild.getData(),cchild.getAttr(attr)
487 return None,None
489 """Returns the presence role (for groupchat)"""
490 return self._muc_getItemAttr('item','role')
492 """Returns the presence affiliation (for groupchat)"""
493 return self._muc_getItemAttr('item','affiliation')
495 """Returns the nick value (for nick change in groupchat)"""
496 return self._muc_getItemAttr('item','nick')
498 """Returns the presence jid (for groupchat)"""
499 return self._muc_getItemAttr('item','jid')
501 """Returns the reason of the presence (for groupchat)"""
502 return self._muc_getSubTagDataAttr('reason','')[0]
504 """Returns the reason of the presence (for groupchat)"""
505 return self._muc_getSubTagDataAttr('actor','jid')[1]
507 """Returns the status code of the presence (for groupchat)"""
508 return self._muc_getItemAttr('status','code')
509
511 """ XMPP Iq object - get/set dialog mechanism. """
512 - def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
513 """ Create Iq object. You can specify type, query namespace
514 any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.
515 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. """
516 Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
517 if payload: self.setQueryPayload(payload)
518 if queryNS: self.setQueryNS(queryNS)
520 """ Return the IQ's child element if it exists, None otherwise."""
521 typ = self.getType()
522 query=None
523 for child in self.getChildren():
524 if ('error' != typ) or ('error' != child.getName()):
525 query=child
526 break
527 return query
529 """ Return the namespace of the 'query' child element."""
530 tag=self.getQuery()
531 if tag: return tag.getNamespace()
533 """ Return the 'node' attribute value of the 'query' child element."""
534 return self.getQuery().getAttr('node')
536 """ Return the 'query' child element payload."""
537 tag=self.getQuery()
538 if tag: return tag.getPayload()
540 """ Return the 'query' child element child nodes."""
541 tag=self.getQuery()
542 if tag: return tag.getChildren()
544 """ Change the name of the query node, creating it if needed. Keep the existing name if none is given (use 'query' if it's a creation). Return the query node."""
545 query=self.getQuery()
546 if query is None:
547 query=self.addChild('query')
548 if name is not None:
549 query.setName(name)
550 return query
552 """ Set the namespace of the 'query' child element."""
553 self.setQuery().setNamespace(namespace)
558 """ Set the 'node' attribute value of the 'query' child element."""
559 self.setQuery().setAttr('node',node)
566
568 """ XMPP-style error element.
569 In the case of stanza error should be attached to XMPP stanza.
570 In the case of stream-level errors should be used separately. """
571 - def __init__(self,name,code=None,typ=None,text=None):
572 """ Create new error node object.
573 Mandatory parameter: name - name of error condition.
574 Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol."""
575 if ERRORS.has_key(name):
576 cod,type,txt=ERRORS[name]
577 ns=name.split()[0]
578 else: cod,ns,type,txt='500',NS_STANZAS,'cancel',''
579 if typ: type=typ
580 if code: cod=code
581 if text: txt=text
582 Node.__init__(self,'error',{},[Node(name)])
583 if type: self.setAttr('type',type)
584 if not cod: self.setName('stream:error')
585 if txt: self.addChild(node=Node(ns+' text',{},[txt]))
586 if cod: self.setAttr('code',cod)
587
589 """ Used to quickly transform received stanza into error reply."""
591 """ Create error reply basing on the received 'node' stanza and the 'error' error condition.
592 If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping)
593 specify the 'reply' argument as false."""
594 if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node)
595 else: Protocol.__init__(self,node=node)
596 self.setError(error)
597 if node.getType()=='error': self.__str__=self.__dupstr__
599 """ Dummy function used as preventor of creating error node in reply to error node.
600 I.e. you will not be able to serialise "double" error into string.
601 """
602 return ''
603
605 """ This class is used in the DataForm class to describe the single data item.
606 If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122)
607 then you will need to work with instances of this class. """
608 - def __init__(self,name=None,value=None,typ=None,required=0,label=None,desc=None,options=[],node=None):
609 """ Create new data field of specified name,value and type.
610 Also 'required','desc' and 'options' fields can be set.
611 Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled.
612 """
613 Node.__init__(self,'field',node=node)
614 if name: self.setVar(name)
615 if type(value) in [list,tuple]: self.setValues(value)
616 elif value: self.setValue(value)
617 if typ: self.setType(typ)
618 elif not typ and not node: self.setType('text-single')
619 if required: self.setRequired(required)
620 if label: self.setLabel(label)
621 if desc: self.setDesc(desc)
622 if options: self.setOptions(options)
624 """ Change the state of the 'required' flag. """
625 if req: self.setTag('required')
626 else:
627 try: self.delChild('required')
628 except ValueError: return
630 """ Returns in this field a required one. """
631 return self.getTag('required')
633 """ Set the label of this field. """
634 self.setAttr('label',label)
636 """ Return the label of this field. """
637 return self.getAttr('label')
639 """ Set the description of this field. """
640 self.setTagData('desc',desc)
642 """ Return the description of this field. """
643 return self.getTagData('desc')
645 """ Set the value of this field. """
646 self.setTagData('value',val)
650 """ Set the values of this field as values-list.
651 Replaces all previous filed values! If you need to just add a value - use addValue method."""
652 while self.getTag('value'): self.delChild('value')
653 for val in lst: self.addValue(val)
655 """ Add one more value to this field. Used in 'get' iq's or such."""
656 self.addChild('value',{},[val])
658 """ Return the list of values associated with this field."""
659 ret=[]
660 for tag in self.getTags('value'): ret.append(tag.getData())
661 return ret
663 """ Return label-option pairs list associated with this field."""
664 ret=[]
665 for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')])
666 return ret
668 """ Set label-option pairs list associated with this field."""
669 while self.getTag('option'): self.delChild('option')
670 for opt in lst: self.addOption(opt)
672 """ Add one more label-option pair to this field."""
673 if type(opt) in [str,unicode]: self.addChild('option').setTagData('value',opt)
674 else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
676 """ Get type of this field. """
677 return self.getAttr('type')
679 """ Set type of this field. """
680 return self.setAttr('type',val)
682 """ Get 'var' attribute value of this field. """
683 return self.getAttr('var')
685 """ Set 'var' attribute value of this field. """
686 return self.setAttr('var',val)
687
689 """ This class is used in the DataForm class to describe the 'reported data field' data items which are used in
690 'multiple item form results' (as described in XEP-0004).
691 Represents the fields that will be returned from a search. This information is useful when
692 you try to use the jabber:iq:search namespace to return dynamic form information.
693 """
695 """ Create new empty 'reported data' field. However, note that, according XEP-0004:
696 * It MUST contain one or more DataFields.
697 * Contained DataFields SHOULD possess a 'type' and 'label' attribute in addition to 'var' attribute
698 * Contained DataFields SHOULD NOT contain a <value/> element.
699 Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new
700 dataitem.
701 """
702 Node.__init__(self,'reported',node=node)
703 if node:
704 newkids=[]
705 for n in self.getChildren():
706 if n.getName()=='field': newkids.append(DataField(node=n))
707 else: newkids.append(n)
708 self.kids=newkids
710 """ Return the datafield object with name 'name' (if exists). """
711 return self.getTag('field',attrs={'var':name})
712 - def setField(self,name,typ=None,label=None):
713 """ Create if nessessary or get the existing datafield object with name 'name' and return it.
714 If created, attributes 'type' and 'label' are applied to new datafield."""
715 f=self.getField(name)
716 if f: return f
717 return self.addChild(node=DataField(name,None,typ,0,label))
719 """ Represent dataitem as simple dictionary mapping of datafield names to their values."""
720 ret={}
721 for field in self.getTags('field'):
722 name=field.getAttr('var')
723 typ=field.getType()
724 if isinstance(typ,(str,unicode)) and typ[-6:]=='-multi':
725 val=[]
726 for i in field.getTags('value'): val.append(i.getData())
727 else: val=field.getTagData('value')
728 ret[name]=val
729 if self.getTag('instructions'): ret['instructions']=self.getInstructions()
730 return ret
732 """ Simple dictionary interface for getting datafields values by their names."""
733 item=self.getField(name)
734 if item: return item.getValue()
735 raise IndexError('No such field')
737 """ Simple dictionary interface for setting datafields values by their names."""
738 return self.setField(name).setValue(val)
739
741 """ This class is used in the DataForm class to describe data items which are used in 'multiple
742 item form results' (as described in XEP-0004).
743 """
745 """ Create new empty data item. However, note that, according XEP-0004, DataItem MUST contain ALL
746 DataFields described in DataReported.
747 Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new
748 dataitem.
749 """
750 Node.__init__(self,'item',node=node)
751 if node:
752 newkids=[]
753 for n in self.getChildren():
754 if n.getName()=='field': newkids.append(DataField(node=n))
755 else: newkids.append(n)
756 self.kids=newkids
758 """ Return the datafield object with name 'name' (if exists). """
759 return self.getTag('field',attrs={'var':name})
761 """ Create if nessessary or get the existing datafield object with name 'name' and return it. """
762 f=self.getField(name)
763 if f: return f
764 return self.addChild(node=DataField(name))
766 """ Represent dataitem as simple dictionary mapping of datafield names to their values."""
767 ret={}
768 for field in self.getTags('field'):
769 name=field.getAttr('var')
770 typ=field.getType()
771 if isinstance(typ,(str,unicode)) and typ[-6:]=='-multi':
772 val=[]
773 for i in field.getTags('value'): val.append(i.getData())
774 else: val=field.getTagData('value')
775 ret[name]=val
776 if self.getTag('instructions'): ret['instructions']=self.getInstructions()
777 return ret
779 """ Simple dictionary interface for getting datafields values by their names."""
780 item=self.getField(name)
781 if item: return item.getValue()
782 raise IndexError('No such field')
784 """ Simple dictionary interface for setting datafields values by their names."""
785 return self.setField(name).setValue(val)
786
878