1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18  """XMPP-IM roster handling. 
 19   
 20  Normative reference: 
 21    - `RFC 3921 <http://www.ietf.org/rfc/rfc3921.txt>`__ 
 22  """ 
 23   
 24  __revision__="$Id: roster.py 647 2006-08-26 18:27:39Z jajcus $" 
 25  __docformat__="restructuredtext en" 
 26   
 27  import libxml2 
 28   
 29  from pyxmpp.xmlextra import common_doc, get_node_ns_uri 
 30  from pyxmpp.iq import Iq 
 31  from pyxmpp.jid import JID 
 32   
 33  from pyxmpp.utils import to_utf8,from_utf8 
 34  from pyxmpp.objects import StanzaPayloadObject 
 35   
 36  ROSTER_NS="jabber:iq:roster" 
 37   
 39      """ 
 40      Roster item. 
 41   
 42      Represents part of a roster, or roster update request. 
 43      """ 
 44   
 45      xml_element_name = "item" 
 46      xml_element_namespace = ROSTER_NS 
 47   
 48 -    def __init__(self,node_or_jid,subscription="none",name=None,groups=(),ask=None): 
  49          """ 
 50          Initialize a roster item from XML node or jid and optional attributes. 
 51   
 52          :Parameters: 
 53              - `node_or_jid`: XML node or JID 
 54              - `subscription`: subscription type ("none", "to", "from" or "both" 
 55              - `name`: item visible name 
 56              - `groups`: sequence of groups the item is member of 
 57              - `ask`: True if there was unreplied subsription or unsubscription 
 58                request sent.""" 
 59          if isinstance(node_or_jid,libxml2.xmlNode): 
 60              self.from_xml(node_or_jid) 
 61          else: 
 62              node_or_jid=JID(node_or_jid) 
 63              if subscription not in ("none","from","to","both","remove"): 
 64                  raise ValueError,"Bad subscription type: %r" % (subscription,) 
 65              if ask not in ("subscribe",None): 
 66                  raise ValueError,"Bad ask type: %r" % (ask,) 
 67              self.jid=node_or_jid 
 68              self.ask=ask 
 69              self.subscription=subscription 
 70              self.name=name 
 71              self.groups=list(groups) 
  72   
 74          """Initialize RosterItem from XML node.""" 
 75          if node.type!="element": 
 76              raise ValueError,"XML node is not a roster item (not en element)" 
 77          ns=get_node_ns_uri(node) 
 78          if ns and ns!=ROSTER_NS or node.name!="item": 
 79              raise ValueError,"XML node is not a roster item" 
 80          jid=JID(node.prop("jid").decode("utf-8")) 
 81          subscription=node.prop("subscription") 
 82          if subscription not in ("none","from","to","both","remove"): 
 83              subscription="none" 
 84          ask=node.prop("ask") 
 85          if ask not in ("subscribe",None): 
 86              ask=None 
 87          name=from_utf8(node.prop("name")) 
 88          groups=[] 
 89          n=node.children 
 90          while n: 
 91              if n.type!="element": 
 92                  n=n.next 
 93                  continue 
 94              ns=get_node_ns_uri(n) 
 95              if ns and ns!=ROSTER_NS or n.name!="group": 
 96                  n=n.next 
 97                  continue 
 98              group=n.getContent() 
 99              if group: 
100                  groups.append(from_utf8(group)) 
101              n=n.next 
102          self.jid=jid 
103          self.name=name 
104          self.groups=groups 
105          self.subscription=subscription 
106          self.ask=ask 
 107   
109          """Complete the XML node with `self` content. 
110   
111          Should be overriden in classes derived from `StanzaPayloadObject`. 
112   
113          :Parameters: 
114              - `xmlnode`: XML node with the element being built. It has already 
115                right name and namespace, but no attributes or content. 
116              - `_unused`: document to which the element belongs. 
117          :Types: 
118              - `xmlnode`: `libxml2.xmlNode` 
119              - `_unused`: `libxml2.xmlDoc`""" 
120          xmlnode.setProp("jid",self.jid.as_utf8()) 
121          if self.name: 
122              xmlnode.setProp("name",to_utf8(self.name)) 
123          xmlnode.setProp("subscription",self.subscription) 
124          if self.ask: 
125              xmlnode.setProp("ask",to_utf8(self.ask)) 
126          for g in self.groups: 
127              xmlnode.newTextChild(None, "group", to_utf8(g)) 
 128   
135   
 145   
146 -class Roster(StanzaPayloadObject): 
 147      """Class representing XMPP-IM roster. 
148   
149      Iteration over `Roster` object iterates over roster items. 
150   
151      ``for item in roster: ...`` may be used to iterate over roster items, 
152      ``roster[jid]`` to get roster item by jid, ``jid in roster`` to test roster 
153      for jid presence. 
154   
155      :Ivariables: 
156          - `items_dict`: items indexed by JID. 
157      :Properties: 
158          - `items`: roster items. 
159      :Types: 
160          - `items_dict`: `dict` of `JID` -> `RosterItem` 
161          - `items`: `list` of `RosterItem`""" 
162   
163      xml_element_name = "query" 
164      xml_element_namespace = ROSTER_NS 
165   
166 -    def __init__(self,node=None,server=False,strict=True): 
 167          """ 
168          Initialize Roster object. 
169   
170          `node` should be an XML representation of the roster (e.g. as sent 
171          from server in response to roster request).  When `node` is None empty 
172          roster will be created. 
173   
174          If `server` is true the object is considered server-side roster. 
175   
176          If `strict` is False, than invalid items in the XML will be ignored. 
177          """ 
178          self.items_dict={} 
179          self.server=server 
180          self.node=None 
181          if node: 
182              self.from_xml(node,strict) 
 183   
185          """ 
186          Initialize Roster object from XML node. 
187   
188          If `strict` is False, than invalid items in the XML will be ignored. 
189          """ 
190          self.items_dict={} 
191          if node.type!="element": 
192              raise ValueError,"XML node is not a roster (not en element)" 
193          ns=get_node_ns_uri(node) 
194          if ns and ns!=ROSTER_NS or node.name!="query": 
195              raise ValueError,"XML node is not a roster" 
196          n=node.children 
197          while n: 
198              if n.type!="element": 
199                  n=n.next 
200                  continue 
201              ns=get_node_ns_uri(n) 
202              if ns and ns!=ROSTER_NS or n.name!="item": 
203                  n=n.next 
204                  continue 
205              try: 
206                  item=RosterItem(n) 
207                  self.items_dict[item.jid]=item 
208              except ValueError: 
209                  if strict: 
210                      raise 
211              n=n.next 
 212   
214          """Complete the XML node with `self` content. 
215   
216          Should be overriden in classes derived from `StanzaPayloadObject`. 
217   
218          :Parameters: 
219              - `xmlnode`: XML node with the element being built. It has already 
220                right name and namespace, but no attributes or content. 
221              - `doc`: document to which the element belongs. 
222          :Types: 
223              - `xmlnode`: `libxml2.xmlNode` 
224              - `doc`: `libxml2.xmlDoc`""" 
225          for it in self.items_dict.values(): 
226              it.as_xml(parent=xmlnode, doc=doc) 
 227   
234       
236          return self.items_dict.itervalues() 
 237   
239          return jid in self.items_dict 
 240   
242          return self.items_dict[jid] 
 243   
245          """Return a list of items in the roster.""" 
246          return self.items_dict.values() 
 247   
248      items = property(get_items) 
249   
251          """Return a list of groups in the roster.""" 
252          r={} 
253          for it in self.items_dict.values(): 
254              it.groups=[g for g in it.groups if g] 
255              if it.groups: 
256                  for g in it.groups: 
257                      r[g]=True 
258              else: 
259                  r[None]=True 
260          return r.keys() 
 261   
263          """ 
264          Return a list of items with given `name`. 
265   
266          If `case_sensitive` is False the matching will be case insensitive. 
267          """ 
268          if not case_sensitive and name: 
269              name = name.lower() 
270          r = [] 
271          for it in self.items_dict.values(): 
272              if it.name == name: 
273                  r.append(it) 
274              elif it.name is None: 
275                  continue 
276              elif not case_sensitive and it.name.lower() == name: 
277                  r.append(it) 
278          return r 
 279   
281          """ 
282          Return a list of groups with given name. 
283   
284          If `case_sensitive` is False the matching will be case insensitive. 
285          """ 
286          r=[] 
287          if not group: 
288              for it in self.items_dict.values(): 
289                  it.groups=[g for g in it.groups if g] 
290                  if not it.groups: 
291                      r.append(it) 
292              return r 
293          if not case_sensitive: 
294              group=group.lower() 
295          for it in self.items_dict.values(): 
296              if group in it.groups: 
297                  r.append(it) 
298              elif not case_sensitive and group in [g.lower() for g in it.groups]: 
299                  r.append(it) 
300          return r 
 301   
303          """ 
304          Return roster item with given `jid`. 
305   
306          :raise KeyError: if the item is not found. 
307          """ 
308          if not jid: 
309              raise ValueError,"jid is None" 
310          return self.items_dict[jid] 
 311   
312 -    def add_item(self,item_or_jid,subscription="none",name=None,groups=(),ask=None): 
 313          """ 
314          Add an item to the roster. 
315   
316          The `item_or_jid` argument may be a `RosterItem` object or a `JID`. If 
317          it is a JID then `subscription`, `name`, `groups` and `ask` may also be 
318          specified. 
319          """ 
320          if isinstance(item_or_jid,RosterItem): 
321              item=item_or_jid 
322              if self.items_dict.has_key(item.jid): 
323                  raise ValueError,"Item already exists" 
324          else: 
325              if self.items_dict.has_key(item_or_jid): 
326                  raise ValueError,"Item already exists" 
327              if not self.server or subscription not in ("none","from","to","both"): 
328                  subscription="none" 
329              if not self.server: 
330                  ask=None 
331              item=RosterItem(item_or_jid,subscription,name,groups,ask) 
332          self.items_dict[item.jid]=item 
333          return item 
 334   
336          """Remove item from the roster.""" 
337          del self.items_dict[jid] 
338          return RosterItem(jid,"remove") 
 339   
341          """ 
342          Apply an update request to the roster. 
343   
344          `query` should be a query included in a "roster push" IQ received. 
345          """ 
346          ctxt=common_doc.xpathNewContext() 
347          ctxt.setContextNode(query) 
348          ctxt.xpathRegisterNs("r",ROSTER_NS) 
349          item=ctxt.xpathEval("r:item") 
350          ctxt.xpathFreeContext() 
351          if not item: 
352              raise ValueError,"No item to update" 
353          item=item[0] 
354          item=RosterItem(item) 
355          jid=item.jid 
356          subscription=item.subscription 
357          try: 
358              local_item=self.get_item_by_jid(jid) 
359              local_item.subscription=subscription 
360          except KeyError: 
361              if subscription=="remove": 
362                  return RosterItem(jid,"remove") 
363              if self.server or subscription not in ("none","from","to","both"): 
364                  subscription="none" 
365              local_item=RosterItem(jid,subscription) 
366          if subscription=="remove": 
367              del self.items_dict[local_item.jid] 
368              return RosterItem(jid,"remove") 
369          local_item.name=item.name 
370          local_item.groups=list(item.groups) 
371          if not self.server: 
372              local_item.ask=item.ask 
373          self.items_dict[local_item.jid]=local_item 
374          return local_item 
  375   
376   
377