mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-23 19:19:53 +00:00
First pass at integrating the new roster manager.
This commit is contained in:
parent
45991e47ee
commit
673545c7e4
9 changed files with 514 additions and 137 deletions
|
@ -15,6 +15,7 @@ import logging
|
||||||
import sleekxmpp
|
import sleekxmpp
|
||||||
from sleekxmpp import plugins
|
from sleekxmpp import plugins
|
||||||
|
|
||||||
|
from sleekxmpp.roster import MultiRoster
|
||||||
from sleekxmpp.stanza import Message, Presence, Iq, Error
|
from sleekxmpp.stanza import Message, Presence, Iq, Error
|
||||||
from sleekxmpp.stanza.roster import Roster
|
from sleekxmpp.stanza.roster import Roster
|
||||||
from sleekxmpp.stanza.nick import Nick
|
from sleekxmpp.stanza.nick import Nick
|
||||||
|
@ -78,7 +79,7 @@ class BaseXMPP(XMLStream):
|
||||||
send_presence_subscribe -- Send a subscription request.
|
send_presence_subscribe -- Send a subscription request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, default_ns='jabber:client'):
|
def __init__(self, jid='', default_ns='jabber:client'):
|
||||||
"""
|
"""
|
||||||
Adapt an XML stream for use with XMPP.
|
Adapt an XML stream for use with XMPP.
|
||||||
|
|
||||||
|
@ -107,10 +108,13 @@ class BaseXMPP(XMLStream):
|
||||||
self.default_ns = default_ns
|
self.default_ns = default_ns
|
||||||
self.stream_ns = 'http://etherx.jabber.org/streams'
|
self.stream_ns = 'http://etherx.jabber.org/streams'
|
||||||
|
|
||||||
self.boundjid = JID("")
|
self.boundjid = JID(jid)
|
||||||
|
|
||||||
self.plugin = {}
|
self.plugin = {}
|
||||||
|
self.rosters = MultiRoster(self)
|
||||||
|
self.rosters.add(self.boundjid.bare)
|
||||||
self.roster = {}
|
self.roster = {}
|
||||||
|
|
||||||
self.is_component = False
|
self.is_component = False
|
||||||
self.auto_authorize = True
|
self.auto_authorize = True
|
||||||
self.auto_subscribe = True
|
self.auto_subscribe = True
|
||||||
|
@ -127,10 +131,20 @@ class BaseXMPP(XMLStream):
|
||||||
MatchXPath("{%s}presence" % self.default_ns),
|
MatchXPath("{%s}presence" % self.default_ns),
|
||||||
self._handle_presence))
|
self._handle_presence))
|
||||||
|
|
||||||
self.add_event_handler('presence_subscribe',
|
|
||||||
self._handle_subscribe)
|
|
||||||
self.add_event_handler('disconnected',
|
self.add_event_handler('disconnected',
|
||||||
self._handle_disconnected)
|
self._handle_disconnected)
|
||||||
|
self.add_event_handler('presence_available', self._handle_available)
|
||||||
|
self.add_event_handler('presence_dnd', self._handle_available)
|
||||||
|
self.add_event_handler('presence_xa', self._handle_available)
|
||||||
|
self.add_event_handler('presence_chat', self._handle_available)
|
||||||
|
self.add_event_handler('presence_away', self._handle_available)
|
||||||
|
self.add_event_handler('presence_unavailable', self._handle_unavailable)
|
||||||
|
self.add_event_handler('presence_subscribe', self._handle_subscribe)
|
||||||
|
self.add_event_handler('presence_subscribed', self._handle_subscribed)
|
||||||
|
self.add_event_handler('presence_unsubscribe', self._handle_unsubscribe)
|
||||||
|
self.add_event_handler('presence_unsubscribed', self._handle_unsubscribed)
|
||||||
|
self.add_event_handler('presence_probe', self._handle_probe)
|
||||||
|
self.add_event_handler('roster_subscription_request', self._handle_new_subscription)
|
||||||
|
|
||||||
# Set up the XML stream with XMPP's root stanzas.
|
# Set up the XML stream with XMPP's root stanzas.
|
||||||
self.registerStanza(Message)
|
self.registerStanza(Message)
|
||||||
|
@ -522,12 +536,49 @@ class BaseXMPP(XMLStream):
|
||||||
"""Process incoming message stanzas."""
|
"""Process incoming message stanzas."""
|
||||||
self.event('message', msg)
|
self.event('message', msg)
|
||||||
|
|
||||||
|
def _handle_available(self, presence):
|
||||||
|
self.rosters[presence['to'].bare][presence['from'].bare].handle_available(presence)
|
||||||
|
|
||||||
|
def _handle_unavailable(self, presence):
|
||||||
|
self.rosters[presence['to'].bare][presence['from'].bare].handle_unavailable(presence)
|
||||||
|
|
||||||
|
def _handle_new_subscription(self, stanza):
|
||||||
|
roster = self.rosters[stanza['to'].bare]
|
||||||
|
item = self.rosters[stanza['to'].bare][stanza['from'].bare]
|
||||||
|
if item['whitelisted']:
|
||||||
|
item.authorize()
|
||||||
|
elif roster.auto_authorize:
|
||||||
|
item.authorize()
|
||||||
|
if roster.auto_subscribe:
|
||||||
|
item.subscribe()
|
||||||
|
elif roster.auto_authorize == False:
|
||||||
|
item.unauthorize()
|
||||||
|
|
||||||
|
def _handle_removed_subscription(self, presence):
|
||||||
|
self.rosters[presence['to'].bare][presence['from'].bare].unauthorize()
|
||||||
|
|
||||||
|
def _handle_subscribe(self, stanza):
|
||||||
|
self.rosters[stanza['to'].bare][stanza['from'].bare].handle_subscribe(stanza)
|
||||||
|
|
||||||
|
def _handle_subscribed(self, stanza):
|
||||||
|
self.rosters[stanza['to'].bare][stanza['from'].bare].handle_subscribed(stanza)
|
||||||
|
|
||||||
|
def _handle_unsubscribe(self, stanza):
|
||||||
|
self.rosters[stanza['to'].bare][stanza['from'].bare].handle_unsubscribe(stanza)
|
||||||
|
|
||||||
|
def _handle_unsubscribed(self, stanza):
|
||||||
|
self.rosters[stanza['to'].bare][stanza['from'].bare].handle_unsubscribed(stanza)
|
||||||
|
|
||||||
|
def _handle_probe(self, stanza):
|
||||||
|
self.rosteritems[stanza['to'].bare][stanza['from'].bare].handle_probe(stanza)
|
||||||
|
|
||||||
def _handle_presence(self, presence):
|
def _handle_presence(self, presence):
|
||||||
"""
|
"""
|
||||||
Process incoming presence stanzas.
|
Process incoming presence stanzas.
|
||||||
|
|
||||||
Update the roster with presence information.
|
Update the roster with presence information.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(presence['type'])
|
||||||
self.event("presence_%s" % presence['type'], presence)
|
self.event("presence_%s" % presence['type'], presence)
|
||||||
|
|
||||||
# Check for changes in subscription state.
|
# Check for changes in subscription state.
|
||||||
|
@ -538,97 +589,7 @@ class BaseXMPP(XMLStream):
|
||||||
elif not presence['type'] in ('available', 'unavailable') and \
|
elif not presence['type'] in ('available', 'unavailable') and \
|
||||||
not presence['type'] in presence.showtypes:
|
not presence['type'] in presence.showtypes:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Strip the information from the stanza.
|
|
||||||
jid = presence['from'].bare
|
|
||||||
resource = presence['from'].resource
|
|
||||||
show = presence['type']
|
|
||||||
status = presence['status']
|
|
||||||
priority = presence['priority']
|
|
||||||
|
|
||||||
was_offline = False
|
|
||||||
got_online = False
|
|
||||||
old_roster = self.roster.get(jid, {}).get(resource, {})
|
|
||||||
|
|
||||||
# Create a new roster entry if needed.
|
|
||||||
if not jid in self.roster:
|
|
||||||
self.roster[jid] = {'groups': [],
|
|
||||||
'name': '',
|
|
||||||
'subscription': 'none',
|
|
||||||
'presence': {},
|
|
||||||
'in_roster': False}
|
|
||||||
|
|
||||||
# Alias to simplify some references.
|
|
||||||
connections = self.roster[jid]['presence']
|
|
||||||
|
|
||||||
# Determine if the user has just come online.
|
|
||||||
if not resource in connections:
|
|
||||||
if show == 'available' or show in presence.showtypes:
|
|
||||||
got_online = True
|
|
||||||
was_offline = True
|
|
||||||
connections[resource] = {}
|
|
||||||
|
|
||||||
if connections[resource].get('show', 'unavailable') == 'unavailable':
|
|
||||||
was_offline = True
|
|
||||||
|
|
||||||
# Update the roster's state for this JID's resource.
|
|
||||||
connections[resource] = {'show': show,
|
|
||||||
'status': status,
|
|
||||||
'priority': priority}
|
|
||||||
|
|
||||||
name = self.roster[jid].get('name', '')
|
|
||||||
|
|
||||||
# Remove unneeded state information after a resource
|
|
||||||
# disconnects. Determine if this was the last connection
|
|
||||||
# for the JID.
|
|
||||||
if show == 'unavailable':
|
|
||||||
log.debug("%s %s got offline" % (jid, resource))
|
|
||||||
del connections[resource]
|
|
||||||
|
|
||||||
if not connections and not self.roster[jid]['in_roster']:
|
|
||||||
del self.roster[jid]
|
|
||||||
if not was_offline:
|
|
||||||
self.event("got_offline", presence)
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
name = '(%s) ' % name if name else ''
|
|
||||||
|
|
||||||
# Presence state has changed.
|
|
||||||
self.event("changed_status", presence)
|
self.event("changed_status", presence)
|
||||||
if got_online:
|
|
||||||
self.event("got_online", presence)
|
|
||||||
log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource,
|
|
||||||
show, status))
|
|
||||||
|
|
||||||
def _handle_subscribe(self, presence):
|
|
||||||
"""
|
|
||||||
Automatically managage subscription requests.
|
|
||||||
|
|
||||||
Subscription behavior is controlled by the settings
|
|
||||||
self.auto_authorize and self.auto_subscribe.
|
|
||||||
|
|
||||||
auto_auth auto_sub Result:
|
|
||||||
True True Create bi-directional subsriptions.
|
|
||||||
True False Create only directed subscriptions.
|
|
||||||
False * Decline all subscriptions.
|
|
||||||
None * Disable automatic handling and use
|
|
||||||
a custom handler.
|
|
||||||
"""
|
|
||||||
presence.reply()
|
|
||||||
presence['to'] = presence['to'].bare
|
|
||||||
|
|
||||||
# We are using trinary logic, so conditions have to be
|
|
||||||
# more explicit than usual.
|
|
||||||
if self.auto_authorize == True:
|
|
||||||
presence['type'] = 'subscribed'
|
|
||||||
presence.send()
|
|
||||||
if self.auto_subscribe:
|
|
||||||
presence['type'] = 'subscribe'
|
|
||||||
presence.send()
|
|
||||||
elif self.auto_authorize == False:
|
|
||||||
presence['type'] = 'unsubscribed'
|
|
||||||
presence.send()
|
|
||||||
|
|
||||||
# Restore the old, lowercased name for backwards compatibility.
|
# Restore the old, lowercased name for backwards compatibility.
|
||||||
basexmpp = BaseXMPP
|
basexmpp = BaseXMPP
|
||||||
|
|
|
@ -66,7 +66,7 @@ class ClientXMPP(BaseXMPP):
|
||||||
when calling register_plugins.
|
when calling register_plugins.
|
||||||
escape_quotes -- Deprecated.
|
escape_quotes -- Deprecated.
|
||||||
"""
|
"""
|
||||||
BaseXMPP.__init__(self, 'jabber:client')
|
BaseXMPP.__init__(self, jid, 'jabber:client')
|
||||||
|
|
||||||
# To comply with PEP8, method names now use underscores.
|
# To comply with PEP8, method names now use underscores.
|
||||||
# Deprecated method names are re-mapped for backwards compatibility.
|
# Deprecated method names are re-mapped for backwards compatibility.
|
||||||
|
@ -75,7 +75,6 @@ class ClientXMPP(BaseXMPP):
|
||||||
self.getRoster = self.get_roster
|
self.getRoster = self.get_roster
|
||||||
self.registerFeature = self.register_feature
|
self.registerFeature = self.register_feature
|
||||||
|
|
||||||
self.set_jid(jid)
|
|
||||||
self.password = password
|
self.password = password
|
||||||
self.escape_quotes = escape_quotes
|
self.escape_quotes = escape_quotes
|
||||||
self.plugin_config = plugin_config
|
self.plugin_config = plugin_config
|
||||||
|
@ -421,13 +420,13 @@ class ClientXMPP(BaseXMPP):
|
||||||
"""
|
"""
|
||||||
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
|
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
|
||||||
for jid in iq['roster']['items']:
|
for jid in iq['roster']['items']:
|
||||||
if not jid in self.roster:
|
item = iq['roster']['items'][jid]
|
||||||
self.roster[jid] = {'groups': [],
|
roster = self.rosters[iq['to'].bare]
|
||||||
'name': '',
|
roster[jid]['name'] = item['name']
|
||||||
'subscription': 'none',
|
roster[jid]['groups'] = item['groups']
|
||||||
'presence': {},
|
roster[jid]['from'] = item['subscription'] in ['from', 'both']
|
||||||
'in_roster': True}
|
roster[jid]['to'] = item['subscription'] in ['to', 'both']
|
||||||
self.roster[jid].update(iq['roster']['items'][jid])
|
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
||||||
|
|
||||||
self.event("roster_update", iq)
|
self.event("roster_update", iq)
|
||||||
if iq['type'] == 'set':
|
if iq['type'] == 'set':
|
||||||
|
|
|
@ -58,7 +58,7 @@ class ComponentXMPP(BaseXMPP):
|
||||||
default_ns = 'jabber:client'
|
default_ns = 'jabber:client'
|
||||||
else:
|
else:
|
||||||
default_ns = 'jabber:component:accept'
|
default_ns = 'jabber:component:accept'
|
||||||
BaseXMPP.__init__(self, default_ns)
|
BaseXMPP.__init__(self, jid, default_ns)
|
||||||
|
|
||||||
self.auto_authorize = None
|
self.auto_authorize = None
|
||||||
self.stream_header = "<stream:stream %s %s to='%s'>" % (
|
self.stream_header = "<stream:stream %s %s to='%s'>" % (
|
||||||
|
@ -68,8 +68,8 @@ class ComponentXMPP(BaseXMPP):
|
||||||
self.stream_footer = "</stream:stream>"
|
self.stream_footer = "</stream:stream>"
|
||||||
self.server_host = host
|
self.server_host = host
|
||||||
self.server_port = port
|
self.server_port = port
|
||||||
self.set_jid(jid)
|
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
|
|
||||||
self.plugin_config = plugin_config
|
self.plugin_config = plugin_config
|
||||||
self.plugin_whitelist = plugin_whitelist
|
self.plugin_whitelist = plugin_whitelist
|
||||||
self.is_component = True
|
self.is_component = True
|
||||||
|
|
374
sleekxmpp/roster.py
Normal file
374
sleekxmpp/roster.py
Normal file
|
@ -0,0 +1,374 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class MultiRoster(object):
|
||||||
|
|
||||||
|
def __init__(self, xmpp, datastore=None):
|
||||||
|
self.xmpp = xmpp
|
||||||
|
self.datastore = datastore
|
||||||
|
self._rosters = {}
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key not in self._rosters:
|
||||||
|
self.add(key)
|
||||||
|
return self._rosters[key]
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self._rosters.keys()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self._rosters.__iter__()
|
||||||
|
|
||||||
|
def add(self, node):
|
||||||
|
if node not in self._rosters:
|
||||||
|
self._rosters[node] = Roster(self.xmpp, node, self.datastore)
|
||||||
|
|
||||||
|
class Roster(object):
|
||||||
|
|
||||||
|
def __init__(self, xmpp, jid, datastore=None):
|
||||||
|
self.xmpp = xmpp
|
||||||
|
self.jid = jid
|
||||||
|
self.datastore = datastore
|
||||||
|
self.auto_authorize = True
|
||||||
|
self.auto_subscribe = True
|
||||||
|
self._jids = {}
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key not in self._jids:
|
||||||
|
self.add(key, save=True)
|
||||||
|
return self._jids[key]
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self._jids.keys()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self._jids.__iter__()
|
||||||
|
|
||||||
|
def add(self, jid, name='', groups=None, afrom=False, ato=False,
|
||||||
|
pending_in=False, pending_out=False, whitelisted=False,
|
||||||
|
save=False):
|
||||||
|
state = {'name': name,
|
||||||
|
'groups': groups or [],
|
||||||
|
'from': afrom,
|
||||||
|
'to': ato,
|
||||||
|
'pending_in': pending_in,
|
||||||
|
'pending_out': pending_out,
|
||||||
|
'whitelisted': whitelisted,
|
||||||
|
'subscription': 'none'}
|
||||||
|
self._jids[jid] = RosterItem(self.xmpp, jid, self.jid,
|
||||||
|
state=state, datastore=self.datastore)
|
||||||
|
if save:
|
||||||
|
self._jids[jid].save()
|
||||||
|
|
||||||
|
def subscribe(self, jid):
|
||||||
|
self._jids[jid].subscribe()
|
||||||
|
|
||||||
|
def unsubscribe(self, jid):
|
||||||
|
self._jids[jid].unsubscribe()
|
||||||
|
|
||||||
|
def remove(self, jid):
|
||||||
|
self._jids[jid].remove()
|
||||||
|
if not self.xmpp.is_component:
|
||||||
|
self.update(jid, subscription='remove')
|
||||||
|
|
||||||
|
def update(self, jid, name=None, subscription=None, groups=[]):
|
||||||
|
self._jids[jid]['name'] = name
|
||||||
|
self._jids[jid]['groups'] = group
|
||||||
|
self._jids[jid].save()
|
||||||
|
|
||||||
|
if not self.xmpp.is_component:
|
||||||
|
iq = self.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['roster']['items'] = {jid: {'name': name,
|
||||||
|
'subscription': subscription,
|
||||||
|
'groups': groups}}
|
||||||
|
response = iq.send()
|
||||||
|
return response and response['type'] == 'result'
|
||||||
|
|
||||||
|
def presence(self, jid, resource=None):
|
||||||
|
if resource is None:
|
||||||
|
return self._jids[jid].resources
|
||||||
|
|
||||||
|
default_presence = {'status': '',
|
||||||
|
'priority': 0,
|
||||||
|
'show': ''}
|
||||||
|
return self._jids[jid].resources.get(resource,
|
||||||
|
default_presence)
|
||||||
|
|
||||||
|
|
||||||
|
class RosterItem(object):
|
||||||
|
|
||||||
|
def __init__(self, xmpp, jid, owner=None,
|
||||||
|
state=None, datastore=None):
|
||||||
|
self.xmpp = xmpp
|
||||||
|
self.jid = jid
|
||||||
|
self.owner = owner or self.xmpp.jid
|
||||||
|
self.last_status = None
|
||||||
|
self.resources = {}
|
||||||
|
self.datastore = datastore
|
||||||
|
|
||||||
|
self._state = state or {
|
||||||
|
'from': False,
|
||||||
|
'to': False,
|
||||||
|
'pending_in': False,
|
||||||
|
'pending_out': False,
|
||||||
|
'whitelisted': False,
|
||||||
|
'subscription': 'none',
|
||||||
|
'name': '',
|
||||||
|
'groups': []}
|
||||||
|
self._datastore_state = {}
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
if self.datastore:
|
||||||
|
item = self.datastore.load(self.owner, self.jid,
|
||||||
|
self._datastore_state)
|
||||||
|
if item:
|
||||||
|
self['name'] = item['name']
|
||||||
|
self['groups'] = item['groups']
|
||||||
|
self['from'] = item['from']
|
||||||
|
self['to'] = item['to']
|
||||||
|
self['whitelisted'] = item['whitelisted']
|
||||||
|
self['pending_out'] = item['pending_out']
|
||||||
|
self['pending_in'] = item['pending_in']
|
||||||
|
self['subscription'] = self._subscription()
|
||||||
|
return self._state
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
if self.datastore:
|
||||||
|
self.datastore.save(self.owner, self.jid,
|
||||||
|
self._state, self._datastore_state)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key in self._state:
|
||||||
|
if key == 'subscription':
|
||||||
|
return self._subscription()
|
||||||
|
return self._state[key]
|
||||||
|
else:
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
print "%s: %s" % (key, value)
|
||||||
|
if key in self._state:
|
||||||
|
if key in ['name', 'subscription', 'groups']:
|
||||||
|
self._state[key] = value
|
||||||
|
else:
|
||||||
|
value = str(value).lower()
|
||||||
|
self._state[key] = value in ('true', '1', 'on', 'yes')
|
||||||
|
else:
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
def _subscription(self):
|
||||||
|
if self['to'] and self['from']:
|
||||||
|
return 'both'
|
||||||
|
elif self['from']:
|
||||||
|
return 'from'
|
||||||
|
elif self['to']:
|
||||||
|
return 'to'
|
||||||
|
else:
|
||||||
|
return 'none'
|
||||||
|
|
||||||
|
def remove(self):
|
||||||
|
"Remove the jids subscription, inform it if it is subscribed, and unwhitelist it"
|
||||||
|
if self['to']:
|
||||||
|
p = self.xmpp.Presence()
|
||||||
|
p['to'] = self.jid
|
||||||
|
p['type'] = ['unsubscribe']
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
p['from'] = self.owner
|
||||||
|
p.send()
|
||||||
|
self['to'] = False
|
||||||
|
self['whitelisted'] = False
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def subscribe(self):
|
||||||
|
p = self.xmpp.Presence()
|
||||||
|
p['to'] = self.jid
|
||||||
|
p['type'] = 'subscribe'
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
p['from'] = self.owner
|
||||||
|
self['pending_out'] = True
|
||||||
|
self.save()
|
||||||
|
p.send()
|
||||||
|
|
||||||
|
def authorize(self):
|
||||||
|
self['from'] = True
|
||||||
|
self['pending_in'] = False
|
||||||
|
self.save()
|
||||||
|
self._subscribed()
|
||||||
|
self.send_last_presence()
|
||||||
|
|
||||||
|
def unauthorize(self):
|
||||||
|
self['from'] = False
|
||||||
|
self['pending_in'] = False
|
||||||
|
self.save()
|
||||||
|
self._unsubscribed()
|
||||||
|
p = self.xmpp.Presence()
|
||||||
|
p['to'] = self.jid
|
||||||
|
p['type'] = 'unavailable'
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
p['from'] = self.owner
|
||||||
|
p.send()
|
||||||
|
|
||||||
|
def _subscribed(self):
|
||||||
|
p = self.xmpp.Presence()
|
||||||
|
p['to'] = self.jid
|
||||||
|
p['type'] = 'subscribed'
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
p['from'] = self.owner
|
||||||
|
p.send()
|
||||||
|
|
||||||
|
def unsubscribe(self):
|
||||||
|
p = self.xmpp.Presence()
|
||||||
|
p['to'] = self.jid
|
||||||
|
p['type'] = 'unsubscribe'
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
p['from'] = self.owner
|
||||||
|
self.save()
|
||||||
|
p.send()
|
||||||
|
|
||||||
|
def _unsubscribed(self):
|
||||||
|
p = self.xmpp.Presence()
|
||||||
|
p['to'] = self.jid
|
||||||
|
p['type'] = 'unsubscribed'
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
p['from'] = self.owner
|
||||||
|
p.send()
|
||||||
|
|
||||||
|
def send_presence(self, ptype='available', status=None):
|
||||||
|
p = self.xmpp.Presence()
|
||||||
|
p['to'] = self.jid
|
||||||
|
p['type'] = ptype
|
||||||
|
p['status'] = status
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
p['from'] = self.owner
|
||||||
|
self.last_status = p
|
||||||
|
p.send()
|
||||||
|
|
||||||
|
def send_last_presence(self):
|
||||||
|
if self.last_status is None:
|
||||||
|
self.send_presence()
|
||||||
|
else:
|
||||||
|
self.last_status.send()
|
||||||
|
|
||||||
|
def handle_available(self, presence):
|
||||||
|
resource = presence['from'].resource
|
||||||
|
data = {'status': presence['status'],
|
||||||
|
'show': presence['show'],
|
||||||
|
'priority': presence['priority']}
|
||||||
|
if not self.resources:
|
||||||
|
self.xmpp.event('got_online', presence)
|
||||||
|
if resource not in self.resources:
|
||||||
|
self.resources[resource] = {}
|
||||||
|
self.resources[resource].update(data)
|
||||||
|
|
||||||
|
def handle_unavailable(self, presence):
|
||||||
|
resource = presence['from'].resource
|
||||||
|
if not self.resources:
|
||||||
|
return
|
||||||
|
if resource in self.resources:
|
||||||
|
del self.resources[resource]
|
||||||
|
if not self.resources:
|
||||||
|
self.xmpp.event('got_offline', presence)
|
||||||
|
|
||||||
|
def handle_subscribe(self, presence):
|
||||||
|
"""
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| EXISTING STATE | DELIVER? | NEW STATE |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| "None" | yes | "None + Pending In" |
|
||||||
|
| "None + Pending Out" | yes | "None + Pending Out/In" |
|
||||||
|
| "None + Pending In" | no | no state change |
|
||||||
|
| "None + Pending Out/In" | no | no state change |
|
||||||
|
| "To" | yes | "To + Pending In" |
|
||||||
|
| "To + Pending In" | no | no state change |
|
||||||
|
| "From" | no * | no state change |
|
||||||
|
| "From + Pending Out" | no * | no state change |
|
||||||
|
| "Both" | no * | no state change |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
if not self['from'] and not self['pending_in']:
|
||||||
|
self['pending_in'] = True
|
||||||
|
self.xmpp.event('roster_subscription_request', presence)
|
||||||
|
elif self['from']:
|
||||||
|
self._subscribed()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def handle_subscribed(self, presence):
|
||||||
|
"""
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| EXISTING STATE | DELIVER? | NEW STATE |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| "None" | no | no state change |
|
||||||
|
| "None + Pending Out" | yes | "To" |
|
||||||
|
| "None + Pending In" | no | no state change |
|
||||||
|
| "None + Pending Out/In" | yes | "To + Pending In" |
|
||||||
|
| "To" | no | no state change |
|
||||||
|
| "To + Pending In" | no | no state change |
|
||||||
|
| "From" | no | no state change |
|
||||||
|
| "From + Pending Out" | yes | "Both" |
|
||||||
|
| "Both" | no | no state change |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
if not self['to'] and self['pending_out']:
|
||||||
|
self['pending_out'] = False
|
||||||
|
self['to'] = True
|
||||||
|
self.xmpp.event('roster_subscription_authorized', presence)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def handle_unsubscribe(self, presence):
|
||||||
|
"""
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| EXISTING STATE | DELIVER? | NEW STATE |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| "None" | no | no state change |
|
||||||
|
| "None + Pending Out" | no | no state change |
|
||||||
|
| "None + Pending In" | yes * | "None" |
|
||||||
|
| "None + Pending Out/In" | yes * | "None + Pending Out" |
|
||||||
|
| "To" | no | no state change |
|
||||||
|
| "To + Pending In" | yes * | "To" |
|
||||||
|
| "From" | yes * | "None" |
|
||||||
|
| "From + Pending Out" | yes * | "None + Pending Out |
|
||||||
|
| "Both" | yes * | "To" |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
if not self['from'] and self['pending_in']:
|
||||||
|
self['pending_in'] = False
|
||||||
|
self._unsubscribed()
|
||||||
|
elif self['from']:
|
||||||
|
self['from'] = False
|
||||||
|
self._unsubscribed()
|
||||||
|
self.xmpp.event('roster_subscription_remove', presence)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def handle_unsubscribed(self, presence):
|
||||||
|
"""
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| EXISTING STATE | DELIVER? | NEW STATE |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| "None" | no | no state change |
|
||||||
|
| "None + Pending Out" | yes | "None" |
|
||||||
|
| "None + Pending In" | no | no state change |
|
||||||
|
| "None + Pending Out/In" | yes | "None + Pending In" |
|
||||||
|
| "To" | yes | "None" |
|
||||||
|
| "To + Pending In" | yes | "None + Pending In" |
|
||||||
|
| "From" | no | no state change |
|
||||||
|
| "From + Pending Out" | yes | "From" |
|
||||||
|
| "Both" | yes | "From" |
|
||||||
|
+------------------------------------------------------------------
|
||||||
|
"""
|
||||||
|
if not self['to'] and self['pending_out']:
|
||||||
|
self['pending_out'] = False
|
||||||
|
elif self['to'] and not self['pending_out']:
|
||||||
|
self['to'] = False
|
||||||
|
self.xmpp.event('roster_subscription_removed', presence)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def handle_probe(self, presence):
|
||||||
|
if self['to']:
|
||||||
|
self.send_last_presence()
|
||||||
|
if self['pending_out']:
|
||||||
|
self.subscribe()
|
||||||
|
if not self['to']:
|
||||||
|
self._unsubscribed()
|
|
@ -106,6 +106,8 @@ class Roster(ElementBase):
|
||||||
item = {}
|
item = {}
|
||||||
item['name'] = itemxml.get('name', '')
|
item['name'] = itemxml.get('name', '')
|
||||||
item['subscription'] = itemxml.get('subscription', '')
|
item['subscription'] = itemxml.get('subscription', '')
|
||||||
|
item['ask'] = itemxml.get('ask', '')
|
||||||
|
item['approved'] = itemxml.get('approved', '')
|
||||||
item['groups'] = []
|
item['groups'] = []
|
||||||
groupsxml = itemxml.findall('{jabber:iq:roster}group')
|
groupsxml = itemxml.findall('{jabber:iq:roster}group')
|
||||||
if groupsxml is not None:
|
if groupsxml is not None:
|
||||||
|
|
|
@ -137,6 +137,33 @@ class SleekTest(unittest.TestCase):
|
||||||
self.assertEqual(str(jid), string,
|
self.assertEqual(str(jid), string,
|
||||||
"String does not match: %s" % str(jid))
|
"String does not match: %s" % str(jid))
|
||||||
|
|
||||||
|
def check_roster(self, owner, jid, name=None, subscription=None,
|
||||||
|
afrom=None, ato=None, pending_out=None, pending_in=None,
|
||||||
|
groups=None):
|
||||||
|
roster = self.xmpp.rosters[owner][jid]
|
||||||
|
print roster._state
|
||||||
|
if name is not None:
|
||||||
|
self.assertEqual(roster['name'], name,
|
||||||
|
"Incorrect name value: %s" % roster['name'])
|
||||||
|
if subscription is not None:
|
||||||
|
self.assertEqual(roster['subscription'], subscription,
|
||||||
|
"Incorrect subscription: %s" % roster['subscription'])
|
||||||
|
if afrom is not None:
|
||||||
|
self.assertEqual(roster['from'], afrom,
|
||||||
|
"Incorrect from state: %s" % roster['from'])
|
||||||
|
if ato is not None:
|
||||||
|
self.assertEqual(roster['to'], ato,
|
||||||
|
"Incorrect to state: %s" % roster['to'])
|
||||||
|
if pending_out is not None:
|
||||||
|
self.assertEqual(roster['pending_out'], pending_out,
|
||||||
|
"Incorrect pending_out state: %s" % roster['pending_out'])
|
||||||
|
if pending_in is not None:
|
||||||
|
self.assertEqual(roster['pending_in'], pending_out,
|
||||||
|
"Incorrect pending_in state: %s" % roster['pending_in'])
|
||||||
|
if groups is not None:
|
||||||
|
self.assertEqual(roster['groups'], groups,
|
||||||
|
"Incorrect groups: %s" % roster['groups'])
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Methods for comparing stanza objects to XML strings
|
# Methods for comparing stanza objects to XML strings
|
||||||
|
|
||||||
|
|
|
@ -48,10 +48,14 @@ class TestRosterStanzas(SleekTest):
|
||||||
'user@example.com': {
|
'user@example.com': {
|
||||||
'name': 'User',
|
'name': 'User',
|
||||||
'subscription': 'both',
|
'subscription': 'both',
|
||||||
|
'ask': '',
|
||||||
|
'approved': '',
|
||||||
'groups': ['Friends', 'Coworkers']},
|
'groups': ['Friends', 'Coworkers']},
|
||||||
'otheruser@example.com': {
|
'otheruser@example.com': {
|
||||||
'name': 'Other User',
|
'name': 'Other User',
|
||||||
'subscription': 'both',
|
'subscription': 'both',
|
||||||
|
'ask': '',
|
||||||
|
'approved': '',
|
||||||
'groups': []}}
|
'groups': []}}
|
||||||
debug = "Roster items don't match after retrieval."
|
debug = "Roster items don't match after retrieval."
|
||||||
debug += "\nReturned: %s" % str(iq['roster']['items'])
|
debug += "\nReturned: %s" % str(iq['roster']['items'])
|
||||||
|
|
|
@ -30,7 +30,9 @@ class TestStreamPresence(SleekTest):
|
||||||
self.xmpp.add_event_handler('presence_unavailable', unavailable)
|
self.xmpp.add_event_handler('presence_unavailable', unavailable)
|
||||||
|
|
||||||
self.recv("""
|
self.recv("""
|
||||||
<presence type="unavailable" from="otheruser@localhost" />
|
<presence type="unavailable"
|
||||||
|
from="otheruser@localhost"
|
||||||
|
to="tester@localhost"/>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Give event queue time to process.
|
# Give event queue time to process.
|
||||||
|
@ -68,12 +70,14 @@ class TestStreamPresence(SleekTest):
|
||||||
|
|
||||||
# Contact comes online.
|
# Contact comes online.
|
||||||
self.recv("""
|
self.recv("""
|
||||||
<presence from="otheruser@localhost/foobar" />
|
<presence from="otheruser@localhost/foobar"
|
||||||
|
to="tester@localhost" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Contact goes offline, should trigger got_offline.
|
# Contact goes offline, should trigger got_offline.
|
||||||
self.recv("""
|
self.recv("""
|
||||||
<presence from="otheruser@localhost/foobar"
|
<presence from="otheruser@localhost/foobar"
|
||||||
|
to="tester@localhost"
|
||||||
type="unavailable" />
|
type="unavailable" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
@ -99,7 +103,8 @@ class TestStreamPresence(SleekTest):
|
||||||
self.xmpp.add_event_handler('got_online', got_online)
|
self.xmpp.add_event_handler('got_online', got_online)
|
||||||
|
|
||||||
self.recv("""
|
self.recv("""
|
||||||
<presence from="user@localhost" />
|
<presence from="user@localhost"
|
||||||
|
to="tester@localhost" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Give event queue time to process.
|
# Give event queue time to process.
|
||||||
|
@ -136,15 +141,23 @@ class TestStreamPresence(SleekTest):
|
||||||
self.xmpp.auto_subscribe = True
|
self.xmpp.auto_subscribe = True
|
||||||
|
|
||||||
self.recv("""
|
self.recv("""
|
||||||
<presence from="user@localhost" type="subscribe" />
|
<presence from="user@localhost"
|
||||||
|
to="tester@localhost"
|
||||||
|
type="subscribe" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.send("""
|
self.send("""
|
||||||
<presence to="user@localhost" type="subscribed" />
|
<presence to="user@localhost"
|
||||||
|
type="subscribed" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.send("""
|
self.send("""
|
||||||
<presence to="user@localhost" type="subscribe" />
|
<presence to="user@localhost" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.send("""
|
||||||
|
<presence to="user@localhost"
|
||||||
|
type="subscribe" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
expected = set(('presence_subscribe', 'changed_subscription'))
|
expected = set(('presence_subscribe', 'changed_subscription'))
|
||||||
|
@ -170,14 +183,17 @@ class TestStreamPresence(SleekTest):
|
||||||
presence_subscribe)
|
presence_subscribe)
|
||||||
|
|
||||||
# With this setting we should reject all subscriptions.
|
# With this setting we should reject all subscriptions.
|
||||||
self.xmpp.auto_authorize = False
|
self.xmpp.rosters['tester@localhost'].auto_authorize = False
|
||||||
|
|
||||||
self.recv("""
|
self.recv("""
|
||||||
<presence from="user@localhost" type="subscribe" />
|
<presence from="user@localhost"
|
||||||
|
to="tester@localhost"
|
||||||
|
type="subscribe" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.send("""
|
self.send("""
|
||||||
<presence to="user@localhost" type="unsubscribed" />
|
<presence to="user@localhost"
|
||||||
|
type="unsubscribed" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
expected = set(('presence_subscribe', 'changed_subscription'))
|
expected = set(('presence_subscribe', 'changed_subscription'))
|
||||||
|
|
|
@ -13,8 +13,7 @@ class TestStreamRoster(SleekTest):
|
||||||
|
|
||||||
def testGetRoster(self):
|
def testGetRoster(self):
|
||||||
"""Test handling roster requests."""
|
"""Test handling roster requests."""
|
||||||
self.stream_start(mode='client')
|
self.stream_start(mode='client', jid='tester@localhost')
|
||||||
self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.")
|
|
||||||
|
|
||||||
# Since get_roster blocks, we need to run it in a thread.
|
# Since get_roster blocks, we need to run it in a thread.
|
||||||
t = threading.Thread(name='get_roster', target=self.xmpp.get_roster)
|
t = threading.Thread(name='get_roster', target=self.xmpp.get_roster)
|
||||||
|
@ -26,11 +25,12 @@ class TestStreamRoster(SleekTest):
|
||||||
</iq>
|
</iq>
|
||||||
""")
|
""")
|
||||||
self.recv("""
|
self.recv("""
|
||||||
<iq type="result" id="1">
|
<iq to='tester@localhost' type="result" id="1">
|
||||||
<query xmlns="jabber:iq:roster">
|
<query xmlns="jabber:iq:roster">
|
||||||
<item jid="user@localhost"
|
<item jid="user@localhost"
|
||||||
name="User"
|
name="User"
|
||||||
subscription="both">
|
subscription="from"
|
||||||
|
ask="subscribe">
|
||||||
<group>Friends</group>
|
<group>Friends</group>
|
||||||
<group>Examples</group>
|
<group>Examples</group>
|
||||||
</item>
|
</item>
|
||||||
|
@ -41,21 +41,20 @@ class TestStreamRoster(SleekTest):
|
||||||
# Wait for get_roster to return.
|
# Wait for get_roster to return.
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
roster = {'user@localhost': {'name': 'User',
|
print self.xmpp.rosters['tester@localhost']['user@localhost']._state
|
||||||
'subscription': 'both',
|
self.check_roster('tester@localhost', 'user@localhost',
|
||||||
'groups': ['Friends', 'Examples'],
|
name='User',
|
||||||
'presence': {},
|
subscription='from',
|
||||||
'in_roster': True}}
|
afrom=True,
|
||||||
self.failUnless(self.xmpp.roster == roster,
|
pending_out=True,
|
||||||
"Unexpected roster values: %s" % self.xmpp.roster)
|
groups=['Friends', 'Examples'])
|
||||||
|
|
||||||
def testRosterSet(self):
|
def testRosterSet(self):
|
||||||
"""Test handling pushed roster updates."""
|
"""Test handling pushed roster updates."""
|
||||||
self.stream_start(mode='client')
|
self.stream_start(mode='client', jid='tester@localhost')
|
||||||
self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.")
|
|
||||||
|
|
||||||
self.recv("""
|
self.recv("""
|
||||||
<iq type="set" id="1">
|
<iq to='tester@localhost' type="set" id="1">
|
||||||
<query xmlns="jabber:iq:roster">
|
<query xmlns="jabber:iq:roster">
|
||||||
<item jid="user@localhost"
|
<item jid="user@localhost"
|
||||||
name="User"
|
name="User"
|
||||||
|
@ -72,15 +71,10 @@ class TestStreamRoster(SleekTest):
|
||||||
</iq>
|
</iq>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
roster = {'user@localhost': {'name': 'User',
|
self.check_roster('tester@localhost', 'user@localhost',
|
||||||
'subscription': 'both',
|
name='User',
|
||||||
'groups': ['Friends', 'Examples'],
|
subscription='both',
|
||||||
'presence': {},
|
groups=['Friends', 'Examples'])
|
||||||
'in_roster': True}}
|
|
||||||
self.failUnless(self.xmpp.roster == roster,
|
|
||||||
"Unexpected roster values: %s" % self.xmpp.roster)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamRoster)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamRoster)
|
||||||
|
|
Loading…
Reference in a new issue