diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 91b419ba..ef6906a6 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -426,7 +426,7 @@ class BaseXMPP(XMLStream): msubject -- Optional subject for the message. mtype -- The message's type, such as 'chat' or 'groupchat'. mhtml -- Optional HTML body content. - mfrom -- The sender of the message. If sending from a client, + mfrom -- The sender of the message. if sending from a client, be aware that some servers require that the full JID of the sender be used. mnick -- Optional nickname of the sender. @@ -441,7 +441,7 @@ class BaseXMPP(XMLStream): return message def make_presence(self, pshow=None, pstatus=None, ppriority=None, - pto=None, ptype=None, pfrom=None): + pto=None, ptype=None, pfrom=None, pnick=None): """ Create and initialize a new Presence stanza. @@ -452,14 +452,16 @@ class BaseXMPP(XMLStream): pto -- The recipient of a directed presence. ptype -- The type of presence, such as 'subscribe'. pfrom -- The sender of the presence. + pnick -- Optional nickname of the presence's sender. """ presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) if pshow is not None: presence['type'] = pshow - if pfrom is None: + if pfrom is None and self.is_component: presence['from'] = self.boundjid.full presence['priority'] = ppriority presence['status'] = pstatus + presence['nick'] = pnick return presence def send_message(self, mto, mbody, msubject=None, mtype=None, @@ -467,13 +469,22 @@ class BaseXMPP(XMLStream): """ Create, initialize, and send a Message stanza. - + Arguments: + mto -- The recipient of the message. + mbody -- The main contents of the message. + msubject -- Optional subject for the message. + mtype -- The message's type, such as 'chat' or 'groupchat'. + mhtml -- Optional HTML body content. + mfrom -- The sender of the message. if sending from a client, + be aware that some servers require that the full JID + of the sender be used. + mnick -- Optional nickname of the sender. """ - self.makeMessage(mto, mbody, msubject, mtype, - mhtml, mfrom, mnick).send() + self.make_message(mto, mbody, msubject, mtype, + mhtml, mfrom, mnick).send() def send_presence(self, pshow=None, pstatus=None, ppriority=None, - pto=None, pfrom=None, ptype=None): + pto=None, pfrom=None, ptype=None, pnick=None): """ Create, initialize, and send a Presence stanza. @@ -484,13 +495,20 @@ class BaseXMPP(XMLStream): pto -- The recipient of a directed presence. ptype -- The type of presence, such as 'subscribe'. pfrom -- The sender of the presence. + pnick -- Optional nickname of the presence's sender. """ - self.makePresence(pshow, pstatus, ppriority, pto, - ptype=ptype, pfrom=pfrom).send() - # Unexpected errors may occur if - if not self.sentpresence: - self.event('sent_presence') - self.sentpresence = True + # Python2.6 chokes on Unicode strings for dict keys. + args = {str('pto'): pto, + str('ptype'): ptype, + str('pshow'): pshow, + str('pstatus'): pstatus, + str('ppriority'): ppriority, + str('pnick'): pnick} + + if self.is_component: + self.roster[pfrom].send_presence(**args) + else: + self.client_roster.send_presence(**args) def send_presence_subscription(self, pto, pfrom=None, ptype='subscribe', pnick=None): diff --git a/sleekxmpp/roster/item.py b/sleekxmpp/roster/item.py index eb115a4a..72baf694 100644 --- a/sleekxmpp/roster/item.py +++ b/sleekxmpp/roster/item.py @@ -105,23 +105,25 @@ class RosterItem(object): """ def __init__(self, xmpp, jid, owner=None, - state=None, db=None): + state=None, db=None, roster=None): """ Create a new roster item. Arguments: - xmpp -- The main SleekXMPP instance. - jid -- The item's JID. - owner -- The roster owner's JID. Defaults - so self.xmpp.boundjid.bare. - state -- A dictionary of initial state values. - db -- An optional interface to an external datastore. + xmpp -- The main SleekXMPP instance. + jid -- The item's JID. + owner -- The roster owner's JID. Defaults + so self.xmpp.boundjid.bare. + state -- A dictionary of initial state values. + db -- An optional interface to an external datastore. + roster -- The roster object containing this entry. """ self.xmpp = xmpp self.jid = jid self.owner = owner or self.xmpp.boundjid.bare self.last_status = None self.resources = {} + self.roster = roster self.db = db self._state = state or { 'from': False, @@ -290,19 +292,46 @@ class RosterItem(object): 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 + def send_presence(self, ptype=None, pshow=None, pstatus=None, + ppriority=None, pnick=None): + """ + Create, initialize, and send a Presence stanza. + + Arguments: + pshow -- The presence's show value. + pstatus -- The presence's status message. + ppriority -- This connections' priority. + ptype -- The type of presence, such as 'subscribe'. + pnick -- Optional nickname of the presence's sender. + """ + p = self.xmpp.make_presence(pshow=pshow, + pstatus=pstatus, + ppriority=ppriority, + ptype=ptype, + pnick=pnick, + pto=self.jid) if self.xmpp.is_component: p['from'] = self.owner - self.last_status = p + if p['type'] in p.showtypes or p['type'] == 'available': + self.last_status = p p.send() + if not self.xmpp.sentpresence: + self.xmpp.event('sent_presence') + self.xmpp.sentpresence = True + def send_last_presence(self): if self.last_status is None: - self.send_presence() + pres = self.roster.last_status + if pres is None: + self.send_presence() + else: + pres['to'] = self.jid + if self.xmpp.is_component: + pres['from'] = self.owner + else: + del pres['from'] + pres.send() else: self.last_status.send() diff --git a/sleekxmpp/roster/multi.py b/sleekxmpp/roster/multi.py index cd4a5195..e9f3389e 100644 --- a/sleekxmpp/roster/multi.py +++ b/sleekxmpp/roster/multi.py @@ -34,7 +34,8 @@ class Roster(object): Defaults to True. Methods: - add -- Create a new roster node for a JID. + add -- Create a new roster node for a JID. + send_presence -- Shortcut for sending a presence stanza. """ def __init__(self, xmpp, db=None): @@ -113,3 +114,27 @@ class Roster(object): """ for node in self: self[node].reset() + + def send_presence(self, pshow=None, pstatus=None, ppriority=None, + pto=None, pfrom=None, ptype=None, pnick=None): + """ + Create, initialize, and send a Presence stanza. + + Forwards the send request to the appropriate roster to + perform the actual sending. + + Arguments: + pshow -- The presence's show value. + pstatus -- The presence's status message. + ppriority -- This connections' priority. + pto -- The recipient of a directed presence. + ptype -- The type of presence, such as 'subscribe'. + pfrom -- The sender of the presence. + pnick -- Optional nickname of the presence's sender. + """ + self[pfrom].send_presence(ptype=ptype, + pshow=pshow, + pstatus=pstatus, + ppriority=ppriority, + pnick=pnick, + pto=pto) diff --git a/sleekxmpp/roster/single.py b/sleekxmpp/roster/single.py index 6ce44e8f..6833563f 100644 --- a/sleekxmpp/roster/single.py +++ b/sleekxmpp/roster/single.py @@ -29,14 +29,17 @@ class RosterNode(object): are created after automatically authrorizing a subscription request. Defaults to True + last_status -- The last sent presence status that was broadcast + to all contact JIDs. Methods: - add -- Add a JID to the roster. - update -- Update a JID's subscription information. - subscribe -- Subscribe to a JID. - unsubscribe -- Unsubscribe from a JID. - remove -- Remove a JID from the roster. - presence -- Return presence information for a JID's resources. + add -- Add a JID to the roster. + update -- Update a JID's subscription information. + subscribe -- Subscribe to a JID. + unsubscribe -- Unsubscribe from a JID. + remove -- Remove a JID from the roster. + presence -- Return presence information for a JID's resources. + send_presence -- Shortcut for sending a presence stanza. """ def __init__(self, xmpp, jid, db=None): @@ -53,6 +56,7 @@ class RosterNode(object): self.db = db self.auto_authorize = True self.auto_subscribe = True + self.last_status = None self._jids = {} if self.db: @@ -135,7 +139,8 @@ class RosterNode(object): 'whitelisted': whitelisted, 'subscription': 'none'} self._jids[jid] = RosterItem(self.xmpp, jid, self.jid, - state=state, db=self.db) + state=state, db=self.db, + roster=self) if save: self._jids[jid].save() @@ -220,3 +225,38 @@ class RosterNode(object): """ for jid in self: self[jid].reset() + + def send_presence(self, ptype=None, pshow=None, pstatus=None, + ppriority=None, pnick=None, pto=None): + """ + Create, initialize, and send a Presence stanza. + + If no recipient is specified, send the presence immediately. + Otherwise, forward the send request to the recipient's roster + entry for processing. + + Arguments: + pshow -- The presence's show value. + pstatus -- The presence's status message. + ppriority -- This connections' priority. + pto -- The recipient of a directed presence. + ptype -- The type of presence, such as 'subscribe'. + """ + if pto: + self[pto].send_presence(ptype, pshow, pstatus, + ppriority, pnick) + else: + p = self.xmpp.make_presence(pshow=pshow, + pstatus=pstatus, + ppriority=ppriority, + ptype=ptype, + pnick=pnick) + if self.xmpp.is_component: + p['from'] = self.jid + if p['type'] in p.showtypes or p['type'] == 'available': + self.last_status = p + p.send() + + if not self.xmpp.sentpresence: + self.xmpp.event('sent_presence') + self.xmpp.sentpresence = True diff --git a/tests/test_stream_roster.py b/tests/test_stream_roster.py index 69e5ca13..aa8fc26f 100644 --- a/tests/test_stream_roster.py +++ b/tests/test_stream_roster.py @@ -203,5 +203,35 @@ class TestStreamRoster(SleekTest): self.failUnless(result == expected, "Unexpected roster values: %s" % result) + def testSendLastPresence(self): + """Test that sending the last presence works.""" + self.stream_start() + self.xmpp.send_presence(pshow='dnd') + self.xmpp.auto_authorize = True + self.xmpp.auto_subscribe = True + + self.send(""" + + dnd + + """) + + self.recv(""" + + """) + + self.send(""" + + """) + + self.send(""" + + dnd + + """) + suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamRoster)