1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """
21 Simple roster implementation. Can be used though for different tasks like
22 mass-renaming of contacts.
23 """
24
25 from protocol import JID, Iq, Presence, Node, NodeProcessed, NS_MUC_USER, NS_ROSTER
26 from plugin import PlugIn
27
28 import logging
29 log = logging.getLogger('nbxmpp.roster_nb')
30
31
33 """
34 Defines a plenty of methods that will allow you to manage roster. Also
35 automatically track presences from remote JIDs taking into account that
36 every JID can have multiple resources connected. Does not currently support
37 'error' presences. You can also use mapping interface for access to the
38 internal representation of contacts in roster
39 """
40
42 """
43 Init internal variables
44 """
45 PlugIn.__init__(self)
46 self.version = version
47 self._data = {}
48 self._set=None
49 self._exported_methods=[self.getRoster]
50 self.received_from_server = False
51
53 """
54 Request roster from server if it were not yet requested (or if the
55 'force' argument is set)
56 """
57 if self._set is None:
58 self._set = 0
59 elif not force:
60 return
61
62 iq = Iq('get', NS_ROSTER)
63 if self.version is not None:
64 iq.setTagAttr('query', 'ver', self.version)
65 id_ = self._owner.getAnID()
66 iq.setID(id_)
67 self._owner.send(iq)
68 log.info('Roster requested from server')
69 return id_
70
72 """
73 Subscription tracker. Used internally for setting items state in internal
74 roster representation
75 """
76 sender = stanza.getAttr('from')
77 if not sender is None and not sender.bareMatch(
78 self._owner.User + '@' + self._owner.Server):
79 return
80 query = stanza.getTag('query')
81 if query:
82 self.received_from_server = True
83 self.version = stanza.getTagAttr('query', 'ver')
84 if self.version is None:
85 self.version = ''
86 for item in query.getTags('item'):
87 jid=item.getAttr('jid')
88 if item.getAttr('subscription')=='remove':
89 if self._data.has_key(jid): del self._data[jid]
90
91
92 log.info('Setting roster item %s...' % jid)
93 if not self._data.has_key(jid): self._data[jid]={}
94 self._data[jid]['name']=item.getAttr('name')
95 self._data[jid]['ask']=item.getAttr('ask')
96 self._data[jid]['subscription']=item.getAttr('subscription')
97 self._data[jid]['groups']=[]
98 if not self._data[jid].has_key('resources'): self._data[jid]['resources']={}
99 for group in item.getTags('group'):
100 if group.getData() not in self._data[jid]['groups']:
101 self._data[jid]['groups'].append(group.getData())
102 self._data[self._owner.User+'@'+self._owner.Server]={'resources': {}, 'name': None, 'ask': None, 'subscription': None, 'groups': None,}
103 self._set=1
104
105
106
108 """
109 Presence tracker. Used internally for setting items' resources state in
110 internal roster representation
111 """
112 if pres.getTag('x', namespace=NS_MUC_USER):
113 return
114 jid=pres.getFrom()
115 if not jid:
116
117 jid=self._owner.Server
118 jid=JID(jid)
119 if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
120 if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
121 self._data[jid.getStripped()]['resources']={}
122 item=self._data[jid.getStripped()]
123 typ=pres.getType()
124
125 if not typ:
126 log.info('Setting roster item %s for resource %s...'%(jid.getStripped(), jid.getResource()))
127 item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
128 if pres.getTag('show'): res['show']=pres.getShow()
129 if pres.getTag('status'): res['status']=pres.getStatus()
130 if pres.getTag('priority'): res['priority']=pres.getPriority()
131 if not pres.getTimestamp(): pres.setTimestamp()
132 res['timestamp']=pres.getTimestamp()
133 elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()]
134
135
137 """
138 Return specific jid's representation in internal format. Used internally
139 """
140 jid = jid[:(jid+'/').find('/')]
141 return self._data[jid][dataname]
142
144 """
145 Return specific jid's resource representation in internal format. Used
146 internally
147 """
148 if jid.find('/') + 1:
149 jid, resource = jid.split('/', 1)
150 if self._data[jid]['resources'].has_key(resource):
151 return self._data[jid]['resources'][resource][dataname]
152 elif self._data[jid]['resources'].keys():
153 lastpri = -129
154 for r in self._data[jid]['resources'].keys():
155 if int(self._data[jid]['resources'][r]['priority']) > lastpri:
156 resource, lastpri=r, int(self._data[jid]['resources'][r]['priority'])
157 return self._data[jid]['resources'][resource][dataname]
158
160 """
161 Delete contact 'jid' from roster
162 """
163 self._owner.send(Iq('set', NS_ROSTER, payload=[Node('item', {'jid': jid, 'subscription': 'remove'})]))
164
166 """
167 Return 'ask' value of contact 'jid'
168 """
169 return self._getItemData(jid, 'ask')
170
172 """
173 Return groups list that contact 'jid' belongs to
174 """
175 return self._getItemData(jid, 'groups')
176
178 """
179 Return name of contact 'jid'
180 """
181 return self._getItemData(jid, 'name')
182
184 """
185 Return priority of contact 'jid'. 'jid' should be a full (not bare) JID
186 """
187 return self._getResourceData(jid, 'priority')
188
190 """
191 Return roster representation in internal format
192 """
193 return self._data
194
196 """
197 Return roster item 'jid' representation in internal format
198 """
199 return self._data[jid[:(jid+'/').find('/')]]
200
202 """
203 Return 'show' value of contact 'jid'. 'jid' should be a full (not bare)
204 JID
205 """
206 return self._getResourceData(jid, 'show')
207
209 """
210 Return 'status' value of contact 'jid'. 'jid' should be a full (not bare)
211 JID
212 """
213 return self._getResourceData(jid, 'status')
214
216 """
217 Return 'subscription' value of contact 'jid'
218 """
219 return self._getItemData(jid, 'subscription')
220
222 """
223 Return list of connected resources of contact 'jid'
224 """
225 return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
226
227 - def setItem(self, jid, name=None, groups=[]):
228 """
229 Rename contact 'jid' and sets the groups list that it now belongs to
230 """
231 iq = Iq('set', NS_ROSTER)
232 query = iq.getTag('query')
233 attrs = {'jid': jid}
234 if name:
235 attrs['name'] = name
236 item = query.setTag('item', attrs)
237 for group in groups:
238 item.addChild(node=Node('group', payload=[group]))
239 self._owner.send(iq)
240
242 """
243 Rename multiple contacts and sets their group lists
244 """
245 iq = Iq('set', NS_ROSTER)
246 query = iq.getTag('query')
247 for i in items:
248 attrs = {'jid': i['jid']}
249 if i['name']:
250 attrs['name'] = i['name']
251 item = query.setTag('item', attrs)
252 for group in i['groups']:
253 item.addChild(node=Node('group', payload=[group]))
254 self._owner.send(iq)
255
257 """
258 Return list of all [bare] JIDs that the roster is currently tracks
259 """
260 return self._data.keys()
261
263 """
264 Same as getItems. Provided for the sake of dictionary interface
265 """
266 return self._data.keys()
267
269 """
270 Get the contact in the internal format. Raises KeyError if JID 'item' is
271 not in roster
272 """
273 return self._data[item]
274
276 """
277 Get the contact in the internal format (or None if JID 'item' is not in
278 roster)
279 """
280 if self._data.has_key(item):
281 return self._data[item]
282
284 """
285 Send subscription request to JID 'jid'
286 """
287 self._owner.send(Presence(jid, 'subscribe'))
288
290 """
291 Ask for removing our subscription for JID 'jid'
292 """
293 self._owner.send(Presence(jid, 'unsubscribe'))
294
296 """
297 Authorize JID 'jid'. Works only if these JID requested auth previously
298 """
299 self._owner.send(Presence(jid, 'subscribed'))
300
302 """
303 Unauthorise JID 'jid'. Use for declining authorisation request or for
304 removing existing authorization
305 """
306 self._owner.send(Presence(jid, 'unsubscribed'))
307
309 """
310 Return the internal data representation of the roster
311 """
312 return self._data
313
315 """
316 Return the internal data representation of the roster
317 """
318 self._data = data
319 self._data[self._owner.User + '@' + self._owner.Server] = {
320 'resources': {},
321 'name': None,
322 'ask': None,
323 'subscription': None,
324 'groups': None
325 }
326 self._set = 1
327
328 - def plugin(self, owner, request=1):
339
341 if data:
342 self._owner.Dispatcher.ProcessNonBlocking(data)
343 if not self._set:
344 return
345 if not hasattr(self, '_owner') or not self._owner:
346
347 return
348 self._owner.onreceive(None)
349 if self.on_ready:
350 self.on_ready(self)
351 self.on_ready = None
352 return True
353
354 - def getRoster(self, on_ready=None, force=False):
355 """
356 Request roster from server if neccessary and returns self
357 """
358 return_self = True
359 if not self._set:
360 self.on_ready = on_ready
361 self._owner.onreceive(self._on_roster_set)
362 return_self = False
363 elif on_ready:
364 on_ready(self)
365 return_self = False
366 if return_self or force:
367 return self
368 return None
369