Skip to content

Commit

Permalink
Merge pull request #122 from sosanzma/documentation
Browse files Browse the repository at this point in the history
Documentation
  • Loading branch information
javipalanca authored Dec 17, 2024
2 parents 42b3a91 + 11f813d commit e1b76a2
Showing 1 changed file with 89 additions and 75 deletions.
164 changes: 89 additions & 75 deletions docs/presence.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,35 @@ Presence Manager
Every SPADE agent has a property to manage its presence. This manager is called ``presence`` and implements all the
methods and attributes to manage an agent's presence notification.

A presence object has three attributes: the **state**, the **status** and the **priority**. Let's see every one of them:
A presence object encapsulates its information in a ``PresenceInfo`` class that contains four main attributes:
**type**, **show**, **status** and **priority**. These attributes work together to provide a complete representation
of an agent's presence state. Let's see every one of them:

State
^^^^^

The state of a presence message shows if the agent is **Available** or **Unavailable**. This means that the agent is
connected to an XMPP server or not. This is very useful to know, before contacting an agent, if it is available to
receive a message in real time or not. The availability state is a boolean attribute.
receive a message in real time or not.

The state is now managed through the ``PresenceType`` enumeration which can have the following values:

- ``PresenceType.AVAILABLE``: The agent is connected and can receive messages
- ``PresenceType.UNAVAILABLE``: The agent is disconnected or not able to receive messages

Besides, the *State* has also an attribute to give additional information about *how available* the contact is. This is
the **Show** attribute. The *Show* attribute belongs to the
class ``aioxmpp.PresenceShow`` and can take the following values:
the **Show** attribute. The *Show* attribute is managed through the ``PresenceShow`` enumeration and can take the following values:

- ``PresenceShow.CHAT``: The entity or resource is actively interested in chatting (i.e. receiving messages).
- ``PresenceShow.AWAY``: The entity or resource is temporarily away, however it can receive messages (they will probably be attended later)
- ``PresenceShow.XA``: The entity or resource is away for an extended period (xa = "*eXtended Away*").
- ``PresenceShow.EXTENDED_AWAY``: The entity or resource is away for an extended period (xa = "*eXtended Away*").
- ``PresenceShow.DND``: The entity or resource is busy (dnd = "*Do Not Disturb*").
- ``PresenceShow.NONE``: Signifies absence of the *Show* element. Used for unavailable states.


An agent can set its availability and show property::

agent.presence.set_available(availability=True, show=PresenceShow.CHAT)
agent.presence.set_presence(presence_type=PresenceType.AVAILABLE, show=PresenceShow.CHAT)


.. warning:: If you set your presence to *unavailable* the only possible show state is ``PresenceShow.NONE``.
Expand All @@ -45,18 +51,16 @@ A short method to set *unavailability* is::
agent.presence.set_unavailable()



To get your presence state::

my_state = agent.presence.state # Gets your current PresenceState instance.

agent.presence.is_available() # Returns a boolean to report wether the agent is available or not
presence_info = agent.presence.get_presence() # Gets your current presence information

my_show = agent.presence.state.show # Gets your current PresenceShow info.
agent.presence.is_available() # Returns a boolean to report whether the agent is available or not

current_show = agent.presence.get_show() # Gets your current PresenceShow info


.. tip:: If no *Show* element is provided, the entity is assumed to be online and available.
.. tip:: If no *Show* element is provided, the entity is assumed to be online and available with ``PresenceShow.NONE``.

Status
^^^^^^
Expand All @@ -66,23 +70,20 @@ your current status which is broadcasted when the client connects and when the p

An agent can get its status as follows::

>> agent.presence.status
{None: "Working..."}
>> agent.presence.get_status()
"Working..."

The status can be set when defining a new presence::

.. warning::
It should be noted that the status is returned as a dict with a ``None`` key. This is because the status supports
different languages. If you set the status as a string it is set as the default status (and stored with the key
``None``. If you want to set the status in different languages you can specify it using the keys::

>> agent.presence.status
{
None: "Working...",
"es": "Trabajando...",
"fr": "Travailler..."
}

agent.presence.set_presence(
presence_type=PresenceType.AVAILABLE,
show=PresenceShow.CHAT,
status="Working on SPADE agents"
)

.. note::
In this new version the status is managed as a simple string value, making it more straightforward
to set and retrieve status information.

Priority
^^^^^^^^
Expand All @@ -91,70 +92,74 @@ Since an agent (and indeed any XMPP user) can have multiple connections to an XM
each of those connections to establish the level of each one. The value must be an integer between -128 and +127.



Setting the Presence
^^^^^^^^^^^^^^^^^^^^

There is a method that can be used to set the three presence attributes. Since they are all optional, you can change any
There is a method that can be used to set all presence attributes. Since they are all optional, you can change any
of the attribute values with every call::

agent.presence.set_presence(
state=PresenceState(True, PresenceShow.CHAT), # available and interested in chatting
status="Lunch",
priority=2
presence_type=PresenceType.AVAILABLE, # set availability
show=PresenceShow.CHAT, # show status
status="Lunch", # status message
priority=2 # connection priority
)



Availability handlers
---------------------
To get notified when a contact gets available or unavailable you can override the ``on_available`` and ``on_unavailable``
handlers. As you can see in the next example, these handlers receive the peer jid of the contact and the *stanza* of
the XMPP Presence message (class ``aioxmpp.Presence``) which contains all its presence information (availability, show,
state, priority, ...)::
handlers. These handlers now receive the peer jid of the contact, the current presence information, and the last known
presence state::

def my_on_available_handler(peer_jid, stanza):
print(f"My friend {peer_jid} is now available with show {stanza.show}")

agent.presence.on_available = my_on_available_handler
def my_on_available_handler(peer_jid, presence_info, last_presence):
print(f"My friend {peer_jid} is now {presence_info.show.value}")
if last_presence:
print(f"Previous state was: {last_presence.show.value}")

agent.presence.on_available = my_on_available_handler

Contact List
------------

Every contact to whom you are subscribed to appears in your *contact list*. You can use the ``get_contacts()`` method to
get the full list of your contacts. This method returns a ``dict`` where the keys are the ``JID`` of your contacts and the
values are an dict that show the information you have about each of your contacts (presence, name, approved,
groups, ask, subscription, ...). Note that the "presence" value is an ``aioxmpp.Presence`` object with the latest updated
information about the contact's presence.
values are ``Contact`` objects that contain all the information about each contact (presence info, name, subscription status,
groups, etc.). The contact's current presence is managed through the ``PresenceInfo`` class.

Example::

>>> contacts = agent.presence.get_contacts()
>>> contacts[myfriend_jid]
{
'presence': Presence(type_=PresenceType.AVAILABLE),
'subscription': 'both',
'name': 'My Friend',
'approved': True
}
Contact(
JID: [email protected],
Name: My Friend,
Presence: PresenceInfo(Type: PresenceType.AVAILABLE, Show: PresenceShow.CHAT, Status: "Working", Priority: 10)
)

You can also get a specific contact using::

contact = agent.presence.get_contact("[email protected]")

.. warning:: An empty contact list will return an empty dictionary.

.. note:: The Contact class provides helper methods like ``is_available()`` and ``is_subscribed()`` to easily check contact status.


Subscribing and unsubscribing to contacts
-----------------------------------------

To subscribe and unsubscribe to/from a contact you have to send a special presence message asking for that subscription.
SPADE helps you by providing some methods that send these special messages::


# Send a subscription request to a peer_jid
agent.presence.subscribe(peer_jid)

# Send an unsubscribe request to a peer_jid
agent.presence.unsubscribe(peer_jid)

# Approve a subscription request
agent.presence.approve_subscription(peer_jid)

Subscription handlers
^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -167,15 +172,15 @@ There are four handlers that you can override to manage these kind of messages:

def my_on_subscribe_callback(peer_jid):
if i_want_to_approve_request:
self.approve(peer_jid)
self.presence.approve_subscription(peer_jid)

agent.presence.on_subscribe = my_on_subscribe_callback


.. note:: In the previous example you can see also how to approve a subscription request by using the ``approve`` method.

.. tip:: If you want to automatically approve all subscription requests you can set the ``PresenceManager.approve_all`` flag to ``True``.
.. note:: In the previous example you can see also how to approve a subscription request by using the ``approve_subscription`` method.

.. tip:: If you want to automatically approve all subscription requests you can set the ``presence.approve_all`` flag to ``True``.

Example
-------
Expand All @@ -188,54 +193,65 @@ This is an example that shows in a practical way the presence module::
import spade
from spade.agent import Agent
from spade.behaviour import OneShotBehaviour
from spade.presence import PresenceType, PresenceShow, PresenceInfo


class Agent1(Agent):
async def setup(self):
print("Agent {} running".format(self.name))
print(f"Agent {self.name} running")
self.add_behaviour(self.Behav1())

class Behav1(OneShotBehaviour):
def on_available(self, jid, stanza):
print("[{}] Agent {} is available.".format(self.agent.name, jid.split("@")[0]))
def on_available(self, peer_jid, presence_info, last_presence):
print(f"[{self.agent.name}] Agent {peer_jid.split('@')[0]} is {presence_info.show.value}")

def on_subscribed(self, jid):
print("[{}] Agent {} has accepted the subscription.".format(self.agent.name, jid.split("@")[0]))
print("[{}] Contacts List: {}".format(self.agent.name, self.agent.presence.get_contacts()))
def on_subscribed(self, peer_jid):
print(f"[{self.agent.name}] Agent {peer_jid.split('@')[0]} has accepted the subscription")
contacts = self.agent.presence.get_contacts()
print(f"[{self.agent.name}] Contacts List: {contacts}")

def on_subscribe(self, jid):
print("[{}] Agent {} asked for subscription. Let's aprove it.".format(self.agent.name, jid.split("@")[0]))
self.presence.approve(jid)
def on_subscribe(self, peer_jid):
print(f"[{self.agent.name}] Agent {peer_jid.split('@')[0]} asked for subscription. Let's approve it")
self.presence.approve_subscription(peer_jid)

async def run(self):
self.presence.on_subscribe = self.on_subscribe
self.presence.on_subscribed = self.on_subscribed
self.presence.on_available = self.on_available

self.presence.set_available()
self.presence.set_presence(
presence_type=PresenceType.AVAILABLE,
show=PresenceShow.CHAT,
status="Ready to chat"
)
self.presence.subscribe(self.agent.jid2)


class Agent2(Agent):
async def setup(self):
print("Agent {} running".format(self.name))
print(f"Agent {self.name} running")
self.add_behaviour(self.Behav2())

class Behav2(OneShotBehaviour):
def on_available(self, jid, stanza):
print("[{}] Agent {} is available.".format(self.agent.name, jid.split("@")[0]))
def on_available(self, peer_jid, presence_info, last_presence):
print(f"[{self.agent.name}] Agent {peer_jid.split('@')[0]} is {presence_info.show.value}")

def on_subscribed(self, jid):
print("[{}] Agent {} has accepted the subscription.".format(self.agent.name, jid.split("@")[0]))
print("[{}] Contacts List: {}".format(self.agent.name, self.agent.presence.get_contacts()))
def on_subscribed(self, peer_jid):
print(f"[{self.agent.name}] Agent {peer_jid.split('@')[0]} has accepted the subscription")
contacts = self.agent.presence.get_contacts()
print(f"[{self.agent.name}] Contacts List: {contacts}")

def on_subscribe(self, jid):
print("[{}] Agent {} asked for subscription. Let's aprove it.".format(self.agent.name, jid.split("@")[0]))
self.presence.approve(jid)
self.presence.subscribe(jid)
def on_subscribe(self, peer_jid):
print(f"[{self.agent.name}] Agent {peer_jid.split('@')[0]} asked for subscription. Let's approve it")
self.presence.approve_subscription(peer_jid)
self.presence.subscribe(peer_jid)

async def run(self):
self.presence.set_available()
self.presence.set_presence(
presence_type=PresenceType.AVAILABLE,
show=PresenceShow.CHAT,
status="Ready to chat"
)
self.presence.on_subscribe = self.on_subscribe
self.presence.on_subscribed = self.on_subscribed
self.presence.on_available = self.on_available
Expand Down Expand Up @@ -266,5 +282,3 @@ This is an example that shows in a practical way the presence module::

if __name__ == "__main__":
spade.run(main())


0 comments on commit e1b76a2

Please sign in to comment.