Skip to content
This repository has been archived by the owner on Feb 19, 2020. It is now read-only.

Roster Management Pre RC1

Max Golubev edited this page Dec 22, 2015 · 1 revision

Roster Management

Background

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.

The Roster

SleekXMPP builds and maintains a local roster based on roster items pushed by the XMPP server. Getting an initial copy of the roster is done using self.getRoster() (where self is a ClientXMPP object), and can then later be accessed with self.roster. The structure for the local roster is:

  • self.roster['user@server']

    The complete record for the subscription to 'user@server'.

  • self.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.</td>

  • self.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 from user@server, but user@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['user@server']['groups']

    A list of groups that user@server has been placed into for ease of use and organization.

  • self.roster['user@server']['presence']

    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, and priority which will contain the associated information from the last presence stanza received from the particular connection. As an exmample, for the agent user@server/foo the roster entry will look like so:

  self.roster['user@server']['presence']['foo'] = {
      'show': 'dnd',
      'status': 'Working on docs for SleekXMPP',
      'priority': '1'
  }

Updating an Entry

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('romeo@montague.lit', 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.

Deleting an Entry

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('rosaline@capulet.lit')
# -or-
self.update_roster('rosaline@capulet.lit', subscription='remove')

Subscription Management

Adding or removing a subscription is typically a four-step process:

  1. Send a presence stanza with type 'subscribe' or 'unsubscribe'
  2. Receive a presence stanza of type 'subscribed' or 'unsubscribed'
  3. Receive a presence stanza of type 'subscribe' or 'unsubscribe'
  4. 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='user@example.com', ptype='subscribe')
# -or-
xmpp.send_presence_subscription(pto='user@example.com', 
                                ptype='subscribe', 
                                pnick='Sleek')

Automatic Subscription Management

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

Custom Subscription Management

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 user1@component.example.com and user2@component.example.com.

# 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 user1@component.example.com and user2@component.example.com.

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'])