-
Notifications
You must be signed in to change notification settings - Fork 299
Roster Management
In XMPP, the presence portion of the protocol is expressed through presence subscriptions between entities. The list of all of these subscriptions is called the roster, and you may think of it as a buddy list or contact list. Manually managing the roster is usually unnecessary since it is done by the server automatically when presence subscribe or unsubscribe requests are received and accepted. However, roster entries may be placed into groups or even removed. In those cases, manual management is needed.
Note: See Stanzas: Presence for more information on adding and removing subscriptions.
If you are using SleekXMPP Beta6.1 or below, see Roster Management Pre RC1.
SleekXMPP builds and maintains a local roster based on roster items pushed
by the XMPP server. For clients, getting an initial copy of the roster is done using
self.get_roster()
(where self
is a ClientXMPP object),
and can then later be accessed with self.client_roster
.
Components may also have a roster, but it is up to the component to manage it, not the server. A component's
roster can be accessed with self.roster
. The difference for components is that the component may be
using multiple JIDs, each of which may have its own set of subscriptions. Thus self.roster
is a collection
of rosters for individual JIDs. For example, self.roster[self.boundjid.bare]
is the roster for the JID
that was used to connect to the server. Clients use the same implementation, and self.client_roster
is
just a shortcut to self.roster[self.boundjid.bare]
.
The structure for an individual roster item (for OWNER_JID
's roster) is:
-
-
self.roster[OWNER_JID]['user@server']['name']
(orself.client_roster['user@server']['name']
) -
The alias assigned to the roster entry to make it easier to identify the owner of the JID. For example, the name of the person using the subscribed JID.
-
-
-
self.roster[OWNER_JID]['user@server']['subscription']
(orself.client_roster['user@server']]['subscription']
) -
The subscription type defines the directions in which presence updates are broadcast. If the subscription type is
'to'
, then the SleekXMPP agent will receive presence updates fromuser@server
, butuser@server
will not receive presence updates from the JID used by the SleekXMPP agent. The direction is reversed if the subscription type is'from'
. Only if the type is'both'
will presence updates be mutually exchanged. No presence updates are broadcast when the type is'none'
. An additional type,'remove'
, is used for deleting a subscription and is described below.Note: If you are using the roster for deciding on message broadcasting recipients, be sure to ignore entries with a subscription type of
'none'
.
-
-
-
self.roster[OWNER_JID]['user@server']['groups']
(orself.client_roster['user@server']['groups']
) -
A list of groups that
user@server
has been placed into for ease of use and organization.
-
-
-
self.roster[OWNER_JID]['user@server'].resources
(orself.client_roster['user@server'].resources
) -
A dictionary of all of the connected resources associated with the given bare JID, keyed by the resource identifier. Each of these dictionaries will contain the keys
show
,status
, andpriority
which will contain the associated information from the last presence stanza received from the particular connection.
-
See the inline documentation for sleekxmpp.roster
for more information. You can also persist the
roster using an external database; see sleekxmpp.roster.item
for details. As an example, here
is a plugin for hooking the roster to Redis: [Redis Roster](https://github.com/legastero/SleekRedis/blob/master/redis_roster.py).
Updating a roster item can be done at any time, but is usually done after a subscription has been established.
# Where self is a ClientXMPP object
self.update_roster('[email protected]', name='Romeo', groups=['Montagues'])
The subscription type can be set as well using the subscription
keyword
parameter. Some servers will issue the appropriate presence stanzas needed
to obtain the requested subscription state, but that behavior should not be
assumed.
Even after modifying a subscription to have a type of 'none'
, the XMPP
server may still keep a record of it and include it in requests for the roster.
For clients that will be adding and removing subscriptions regularly and in
large numbers (such as a public bot), then removing obsolete entries is needed.
Updating a roster item to have a subscription type of 'remove'
will instruct
the server to delete the entry from its data store and not include it in future
roster requests.
# Where self is a ClientXMPP object
self.del_roster_item('[email protected]')
# -or-
self.update_roster('[email protected]', subscription='remove')
Adding or removing a subscription is typically a four-step process:
- Send a presence stanza with type
'subscribe'
or'unsubscribe'
- Receive a presence stanza of type
'subscribed'
or'unsubscribed'
- Receive a presence stanza of type
'subscribe'
or'unsubscribe'
- Send a presence stanza of type
'subscribed'
or'unsubscribed'
If your program will be accepting subscriptions instead of initiating them, reverse the send and receive steps above.
Sending and replying to a presence subscription can be done using
xmpp.send_presence
or xmpp.send_presence_subscription
. In both cases, the
ptype
keyword parameter must be set. If adding a nick
element to the
subscription request is desired, xmpp.send_presence_subscription
must be used.
# Where xmpp is a SleekXMPP object
xmpp.send_presence(pto='[email protected]', ptype='subscribe')
# -or-
xmpp.send_presence_subscription(pto='[email protected]',
ptype='subscribe',
pnick='Sleek')
SleekXMPP already provides basic subscription management, but the automatic handling is to either accept or reject all subscription requests.
The value xmpp.auto_authorize
controls how the agent responds to a
'subscribe'
request. If True
, then a 'subscribed'
response will be
sent. If False
, then an 'unsubscribed'
response will be sent to decline
the subscription. Setting xmpp.auto_authorize
to None
will disable
automatic management.
NOTE: By default, xmpp.auto_authorize
is set to True
.
The value xmpp.auto_subscribe
is used when xmpp.auto_authorize
is set to
True
. If xmpp.auto_subscribe
is True
then a 'subscribe'
request
will be sent after accepting a subscription request.
- Accept and create bidirectional subscription requests:
xmpp.auto_authorize = True
xmpp.auto_subscribe = True
- Accept only one-directional subscription requests:
xmpp.auto_authorize = True
xmpp.auto_subscribe = False
- Decline all subscription requests:
xmpp.auto_authorize = False
- Use a custom subscription policy:
xmpp.auto_authorize = None
To create custom subscription management, the events 'presence_subscribe'
,
'presence_subscribed'
, 'presence_unsubscribe'
, and
'presence_unsubscribed'
must be listened for by event handlers.
Note: If your application will both send and receive subscription requests, you will need to keep a roster data structure that knows the status of all pending requests (the ClientXMPP implementation has such a roster object; the ComponentXMPP implementation does not unless you use the experimental roster branch). Otherwise, you can create an infinite loop of two bots subscribing to the other's presence. If you only test with a human-facing client, most client programs will detect that situation and stop the cycle without reporting an error to you.
As an example, here is a sample snippet for custom subscription handling.
However, note that if you are using a server component, it is a good idea
to also set the pfrom
parameter when sending presence subscriptions.
Doing so allows you to support different subscription states for different
JIDs for the component, such as [email protected]
and
[email protected]
.
# Where self is a SleekXMPP object, and self.backend is some arbitrary
# object that you create depending on the needs of your application.
# self.add_event_handler('presence_subscribe', self.subscribe)
# self.add_event_handler('presence_subscribed', self.subscribed)
# The unsubscribe and unsubscribed handlers will be similar.
# If a component is being used, be sure to set the pfrom parameter
# when sending presences if you are using multiple JIDs for the component,
# such as [email protected] and [email protected].
def subscribe(self, presence):
# If the subscription request is rejected.
if not self.backend.allow_subscription(presence['from']):
self.send_presence(pto=presence['from'],
ptype='unsubscribed')
return
# If the subscription request is accepted.
self.send_presence(pto=presence['from'],
ptype='subscribed')
# Save the fact that a subscription has been accepted, somehow. Here
# we use a backend object that has a roster.
self.backend.roster.subscribe(presence['from'])
# If a bi-directional subscription does not exist, create one.
if not self.backend.roster.sub_from(presence['from']):
self.send_presence(pto=presence['from'],
ptype='subscribe')
def subscribed(self, presence):
# Store the new subscription state, somehow. Here we use a backend object.
self.backend.roster.subscribed(presence['from'])
# Send a new presence update to the subscriber.
self.send_presence(pto=presence['from'])