* started converstion to stanza objects

This commit is contained in:
Nathan Fritz 2009-12-11 01:29:46 +00:00
parent a031dd24a6
commit 8854509ccd
7 changed files with 276 additions and 347 deletions

View file

@ -75,6 +75,7 @@ class ClientXMPP(basexmpp, XMLStream):
#self.map_namespace('http://etherx.jabber.org/streams', 'stream') #self.map_namespace('http://etherx.jabber.org/streams', 'stream')
#self.map_namespace('jabber:client', '') #self.map_namespace('jabber:client', '')
self.features = [] self.features = []
#TODO: Use stream state here
self.authenticated = False self.authenticated = False
self.sessionstarted = False self.sessionstarted = False
self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True))
@ -88,14 +89,6 @@ class ClientXMPP(basexmpp, XMLStream):
#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType) #self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
#self.register_plugins() #self.register_plugins()
def importStanzas(self):
pass
return
for modname in stanza.__all__:
__import__("%s.%s" % (globals()['stanza'].__name__, modname))
for register in getattr(stanza, modname).stanzas:
self.registerStanza(**register)
def __getitem__(self, key): def __getitem__(self, key):
if key in self.plugin: if key in self.plugin:
return self.plugin[key] return self.plugin[key]
@ -109,7 +102,6 @@ class ClientXMPP(basexmpp, XMLStream):
def connect(self, address=tuple()): def connect(self, address=tuple()):
"""Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses """Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses
the JID server.""" the JID server."""
self.importStanzas()
if not address or len(address) < 2: if not address or len(address) < 2:
if not self.srvsupport: if not self.srvsupport:
logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using server hostname from JID.") logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using server hostname from JID.")
@ -163,26 +155,15 @@ class ClientXMPP(basexmpp, XMLStream):
def updateRoster(self, jid, name=None, subscription=None, groups=[]): def updateRoster(self, jid, name=None, subscription=None, groups=[]):
"""Add or change a roster item.""" """Add or change a roster item."""
iq = self.makeIqSet() iq = self.Iq().setValues({'type': 'set'})
iq.attrib['from'] = self.fulljid iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}}
query = self.makeQueryRoster(iq) #self.send(iq, self.Iq().setValues({'id': iq['id']}))
item = ET.Element('item') r = iq.send()
item.attrib['jid'] = jid return r['type'] == 'result'
if name:
item.attrib['name'] = name
if subscription in ['to', 'from', 'both']:
item.attrib['subscription'] = subscription
else:
item.attrib['subscription'] = 'none'
for group in groups:
groupxml = ET.Element('group')
groupxml.text = group
item.append.groupxml
return self.send(iq, self.makeIq(self.getId()))
def getRoster(self): def getRoster(self):
"""Request the roster be sent.""" """Request the roster be sent."""
self.send(self.makeIqGet('jabber:iq:roster')) self.Iq().setValues({'type': 'get'}).enable('roster').send()
def _handleStreamFeatures(self, features): def _handleStreamFeatures(self, features):
self.features = [] self.features = []
@ -198,7 +179,7 @@ class ClientXMPP(basexmpp, XMLStream):
def handler_starttls(self, xml): def handler_starttls(self, xml):
if not self.authenticated and self.ssl_support: if not self.authenticated and self.ssl_support:
self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, instream=True) self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, instream=True)
self.send(xml) self.sendXML(xml)
return True return True
else: else:
logging.warning("The module tlslite is required in to some servers, and has not been found.") logging.warning("The module tlslite is required in to some servers, and has not been found.")
@ -240,14 +221,14 @@ class ClientXMPP(basexmpp, XMLStream):
def handler_bind_resource(self, xml): def handler_bind_resource(self, xml):
logging.debug("Requesting resource: %s" % self.resource) logging.debug("Requesting resource: %s" % self.resource)
out = self.makeIqSet() iq = self.Iq(stype='set')
res = ET.Element('resource') res = ET.Element('resource')
res.text = self.resource res.text = self.resource
xml.append(res) xml.append(res)
out.append(xml) iq.append(xml)
id = out.get('id') response = iq.send()
response = self.send(out, self.makeIqResult(id)) #response = self.send(iq, self.Iq(sid=iq['id']))
self.set_jid(response.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text) self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text)
logging.info("Node set to: %s" % self.fulljid) logging.info("Node set to: %s" % self.fulljid)
if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features: if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features:
logging.debug("Established Session") logging.debug("Established Session")
@ -261,21 +242,11 @@ class ClientXMPP(basexmpp, XMLStream):
self.sessionstarted = True self.sessionstarted = True
self.event("session_start") self.event("session_start")
def _handleRoster(self, roster): def _handleRoster(self, iq):
xml = roster.xml for jid in iq['roster']['items']:
xml = roster.xml if not jid.bare in self.roster:
roster_update = {} self.roster[jid.bare] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
for item in xml.findall('{jabber:iq:roster}query/{jabber:iq:roster}item'): self.roster[jid.bare].update(iq['roster']['jid'])
if not item.attrib['jid'] in self.roster: if iq['type'] == 'set':
self.roster[item.attrib['jid']] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': False} self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster'))
self.roster[item.attrib['jid']]['name'] = item.get('name', '') self.event("roster_update", iq)
self.roster[item.attrib['jid']]['subscription'] = item.get('subscription', 'none')
self.roster[item.attrib['jid']]['in_roster'] = 'True'
for group in item.findall('{jabber:iq:roster}group'):
self.roster[item.attrib['jid']]['groups'].append(group.text)
if self.roster[item.attrib['jid']]['groups'] == []:
self.roster[item.attrib['jid']]['groups'].append('Default')
roster_update[item.attrib['jid']] = self.roster[item.attrib['jid']]
if xml.get('type', 'result') == 'set':
self.send(self.makeIqResult(xml.get('id', '0')))
self.event("roster_update", roster_update)

View file

@ -28,61 +28,22 @@ from . xmlstream.handler.callback import Callback
from . import plugins from . import plugins
from . stanza.message import Message from . stanza.message import Message
from . stanza.iq import Iq from . stanza.iq import Iq
from . stanza.presence import Presence
from . stanza.roster import Roster
import logging import logging
import threading import threading
def stanzaPlugin(stanza, plugin):
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
stanzaPlugin(Iq, Roster)
class basexmpp(object): class basexmpp(object):
def __init__(self): def __init__(self):
self.id = 0 self.id = 0
self.id_lock = threading.Lock() self.id_lock = threading.Lock()
self.stanza_errors = {
'bad-request':False,
'conflict':False,
'feature-not-implemented':False,
'forbidden':False,
'gone':True,
'internal-server-error':False,
'item-not-found':False,
'jid-malformed':False,
'not-acceptable':False,
'not-allowed':False,
'payment-required':False,
'recipient-unavailable':False,
'redirect':True,
'registration-required':False,
'remote-server-not-found':False,
'remote-server-timeout':False,
'resource-constraint':False,
'service-unavailable':False,
'subscription-required':False,
'undefined-condition':False,
'unexpected-request':False}
self.stream_errors = {
'bad-format':False,
'bad-namespace-prefix':False,
'conflict':False,
'connection-timeout':False,
'host-gone':False,
'host-unknown':False,
'improper-addressing':False,
'internal-server-error':False,
'invalid-from':False,
'invalid-id':False,
'invalid-namespace':False,
'invalid-xml':False,
'not-authorized':False,
'policy-violation':False,
'remote-connection-failed':False,
'resource-constraint':False,
'restricted-xml':False,
'see-other-host':True,
'system-shutdown':False,
'undefined-condition':False,
'unsupported-encoding':False,
'unsupported-stanza-type':False,
'unsupported-version':False,
'xml-not-well-formed':False}
self.sentpresence = False self.sentpresence = False
self.fulljid = '' self.fulljid = ''
self.resource = '' self.resource = ''
@ -94,11 +55,21 @@ class basexmpp(object):
self.auto_subscribe = True self.auto_subscribe = True
self.event_handlers = {} self.event_handlers = {}
self.roster = {} self.roster = {}
self.registerHandler(Callback('IM', MatchMany((MatchXMLMask("<message xmlns='%s' type='chat'><body /></message>" % self.default_ns),MatchXMLMask("<message xmlns='%s' type='normal'><body /></message>" % self.default_ns),MatchXMLMask("<message xmlns='%s' type='__None__'><body /></message>" % self.default_ns))), self._handleMessage, thread=False)) self.registerHandler(Callback('IM', MatchXMLMask("<message xmlns='%s'><body /></message>" % self.default_ns), self._handleMessage))
self.registerHandler(Callback('Presence', MatchMany((MatchXMLMask("<presence xmlns='%s' type='available'/>" % self.default_ns),MatchXMLMask("<presence xmlns='%s' type='__None__'/>" % self.default_ns),MatchXMLMask("<presence xmlns='%s' type='unavailable'/>" % self.default_ns))), self._handlePresence, thread=False)) self.registerHandler(Callback('Presence', MatchXMLMask("<presence xmlns='%s' />" % self.default_ns), self._handlePresence))
self.registerHandler(Callback('PresenceSubscribe', MatchMany((MatchXMLMask("<presence xmlns='%s' type='subscribe'/>" % self.default_ns),MatchXMLMask("<presence xmlns='%s' type='unsubscribed'/>" % self.default_ns))), self._handlePresenceSubscribe)) self.add_event_handler('presence_subscribe', self._handlePresenceSubscribe)
self.registerStanza(Message) self.registerStanza(Message)
self.registerStanza(Iq) self.registerStanza(Iq)
self.registerStanza(Presence)
def Message(self, *args, **kwargs):
return Presence(self, *args, **kwargs)
def Iq(self, *args, **kwargs):
return Iq(self, *args, **kwargs)
def Presence(self, *args, **kwargs):
return Presence(self, *args, **kwargs)
def set_jid(self, jid): def set_jid(self, jid):
"""Rip a JID apart and claim it as our own.""" """Rip a JID apart and claim it as our own."""
@ -147,11 +118,17 @@ class basexmpp(object):
def getId(self): def getId(self):
return "%x".upper() % self.id return "%x".upper() % self.id
def sendXML(self, data, mask=None, timeout=10):
return self.send(self.tostring(data), mask, timeout)
def send(self, data, mask=None, timeout=60): def send(self, data, mask=None, timeout=10):
#logging.warning("Deprecated send used for \"%s\"" % (data,)) #logging.warning("Deprecated send used for \"%s\"" % (data,))
if not type(data) == type(''): #if not type(data) == type(''):
data = self.tostring(data) # data = self.tostring(data)
if hasattr(mask, 'xml'):
mask = mask.xml
data = str(data)
if mask is not None: if mask is not None:
waitfor = XMLWaiter('SendWait_%s' % self.getNewId(), MatchXMLMask(mask)) waitfor = XMLWaiter('SendWait_%s' % self.getNewId(), MatchXMLMask(mask))
self.registerHandler(waitfor) self.registerHandler(waitfor)
@ -160,86 +137,28 @@ class basexmpp(object):
return waitfor.wait(timeout) return waitfor.wait(timeout)
def makeIq(self, id=0, ifrom=None): def makeIq(self, id=0, ifrom=None):
iq = ET.Element('{%s}iq' % self.default_ns) return self.Iq().setValues({'id': id, 'from': ifrom})
if id == 0:
id = self.getNewId()
iq.set('id', str(id))
if ifrom is not None:
iq.attrib['from'] = ifrom
return iq
def makeIqGet(self, queryxmlns = None): def makeIqGet(self, queryxmlns = None):
iq = self.makeIq() iq = self.Iq().setValues({'type': 'get'})
iq.set('type', 'get')
if queryxmlns: if queryxmlns:
query = ET.Element("{%s}query" % queryxmlns) iq.append(ET.Element("{%s}query" % queryxmlns))
iq.append(query)
return iq return iq
def makeIqResult(self, id): def makeIqResult(self, id):
iq = self.makeIq(id) return self.Iq().setValues({'id': id, 'type': 'result'})
iq.set('type', 'result')
return iq
def makeIqSet(self, sub=None): def makeIqSet(self, sub=None):
iq = self.makeIq() iq = self.Iq().setValues({'type': 'set'})
iq.set('type', 'set')
if sub != None: if sub != None:
iq.append(sub) iq.append(sub)
return iq return iq
def makeIqError(self, id): def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None):
iq = self.makeIq(id) iq = self.Iq().setValues({'id': id})
iq.set('type', 'error') iq['error'].setValues({'type': type, 'condition': condition, 'text': text})
return iq return iq
def makeStanzaErrorCondition(self, condition, cdata=None):
if condition not in self.stanza_errors:
raise ValueError()
stanzaError = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}'+condition)
if cdata is not None:
if not self.stanza_errors[condition]:
raise ValueError()
stanzaError.text = cdata
return stanzaError
def makeStanzaError(self, condition, errorType, code=None, text=None, customElem=None):
if errorType not in ['auth', 'cancel', 'continue', 'modify', 'wait']:
raise ValueError()
error = ET.Element('error')
error.append(self.makeStanzaErrorCondition(condition))
error.set('type',errorType)
if code is not None:
error.set('code', code)
if text is not None:
textElem = ET.Element('text')
textElem.text = text
error.append(textElem)
if customElem is not None:
error.append(customElem)
return error
def makeStreamErrorCondition(self, condition, cdata=None):
if condition not in self.stream_errors:
raise ValueError()
streamError = ET.Element('{urn:ietf:params:xml:ns:xmpp-streams}'+condition)
if cdata is not None:
if not self.stream_errors[condition]:
raise ValueError()
textElem = ET.Element('text')
textElem.text = text
streamError.append(textElem)
def makeStreamError(self, errorElem, text=None):
error = ET.Element('error')
error.append(errorElem)
if text is not None:
textElem = ET.Element('text')
textElem.text = text
error.append(text)
return error
def makeIqQuery(self, iq, xmlns): def makeIqQuery(self, iq, xmlns):
query = ET.Element("{%s}query" % xmlns) query = ET.Element("{%s}query" % xmlns)
iq.append(query) iq.append(query)
@ -355,61 +274,21 @@ class basexmpp(object):
def _handleMessage(self, msg): def _handleMessage(self, msg):
self.event('message', msg) self.event('message', msg)
#xml = msg.xml
#ns = xml.tag.split('}')[0]
#if ns == 'message':
# ns = ''
#else:
# ns = "%s}" % ns
#mfrom = xml.attrib['from']
#message = xml.find('%sbody' % ns).text
#subject = xml.find('%ssubject' % ns)
#if subject is not None:
# subject = subject.text
#else:
# subject = ''
#resource = self.getjidresource(mfrom)
#mfrom = self.getjidbare(mfrom)
#mtype = xml.attrib.get('type', 'normal')
#name = self.roster.get('name', '')
#self.event("message", {'jid': mfrom, 'resource': resource, 'name': name, 'type': mtype, 'subject': subject, 'message': message, 'to': xml.attrib.get('to', '')})
def _handlePresence(self, presence): def _handlePresence(self, presence):
xml = presence.xml
ns = xml.tag.split('}')[0]
if ns == 'presence':
ns = ''
else:
ns = "%s}" % ns
"""Update roster items based on presence""" """Update roster items based on presence"""
show = xml.find('%sshow' % ns) self.event("presence_%s" % presence['type'], presence)
status = xml.find('%sstatus' % ns) if not presence['type'] in ('available', 'unavailable'):
priority = xml.find('%spriority' % ns) return
fulljid = xml.attrib['from'] jid = presence['from'].bare
to = xml.attrib['to'] resource = presence['from'].resource
resource = self.getjidresource(fulljid) show = presence['type']
if not resource: status = presence['status']
resouce = None priority = presence['priority']
jid = self.getjidbare(fulljid)
if type(status) == type(None) or status.text is None:
status = ''
else:
status = status.text
if type(show) == type(None):
show = 'available'
else:
show = show.text
if xml.get('type', None) == 'unavailable':
show = 'unavailable'
if type(priority) == type(None):
priority = 0
else:
priority = int(priority.text)
wasoffline = False wasoffline = False
oldroster = self.roster.get(jid, {}).get(resource, {}) oldroster = self.roster.get(jid, {}).get(resource, {})
if not jid in self.roster: if not presence['from'].bare in self.roster:
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': False} self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': False}
if not resource in self.roster[jid]['presence']: if not resource in self.roster[jid]['presence']:
wasoffline = True wasoffline = True
self.roster[jid]['presence'][resource] = {'show': show, 'status': status, 'priority': priority} self.roster[jid]['presence'][resource] = {'show': show, 'status': status, 'priority': priority}
@ -417,12 +296,10 @@ class basexmpp(object):
if self.roster[jid]['presence'][resource].get('show', None) == 'unavailable': if self.roster[jid]['presence'][resource].get('show', None) == 'unavailable':
wasoffline = True wasoffline = True
self.roster[jid]['presence'][resource] = {'show': show, 'status': status} self.roster[jid]['presence'][resource] = {'show': show, 'status': status}
if priority: self.roster[jid]['presence'][resource]['priority'] = priority
self.roster[jid]['presence'][resource]['priority'] = priority
name = self.roster[jid].get('name', '') name = self.roster[jid].get('name', '')
eventdata = {'jid': jid, 'to': to, 'resource': resource, 'name': name, 'type': show, 'priority': priority, 'message': status} if wasoffline and show in ('available', 'away', 'xa', 'na', 'ffc'):
if wasoffline and show in ('available', 'away', 'xa', 'na'): self.event("got_online", presence)
self.event("got_online", eventdata)
elif not wasoffline and show == 'unavailable': elif not wasoffline and show == 'unavailable':
self.event("got_offline", eventdata) self.event("got_offline", eventdata)
if len(self.roster[jid]['presence']) > 1: if len(self.roster[jid]['presence']) > 1:
@ -430,7 +307,7 @@ class basexmpp(object):
else: else:
del self.roster[jid] del self.roster[jid]
elif oldroster != self.roster.get(jid, {'presence': {}})['presence'].get(resource, {}) and show != 'unavailable': elif oldroster != self.roster.get(jid, {'presence': {}})['presence'].get(resource, {}) and show != 'unavailable':
self.event("changed_status", eventdata) self.event("changed_status", presence)
name = '' name = ''
if name: if name:
name = "(%s) " % name name = "(%s) " % name
@ -438,13 +315,9 @@ class basexmpp(object):
def _handlePresenceSubscribe(self, presence): def _handlePresenceSubscribe(self, presence):
"""Handling subscriptions automatically.""" """Handling subscriptions automatically."""
xml = presence.xml
if self.auto_authorize == True: if self.auto_authorize == True:
#self.updateRoster(self.getjidbare(xml.attrib['from'])) self.send(self.makePresence(ptype='subscribed', pto=presence['from'].bare))
self.send(self.makePresence(ptype='subscribed', pto=self.getjidbare(xml.attrib['from'])))
if self.auto_subscribe: if self.auto_subscribe:
self.send(self.makePresence(ptype='subscribe', pto=self.getjidbare(xml.attrib['from']))) self.send(self.makePresence(ptype='subscribe', pto=presence['from'].bare))
elif self.auto_authorize == False: elif self.auto_authorize == False:
self.send(self.makePresence(ptype='unsubscribed', pto=self.getjidbare(xml.attrib['from']))) self.send(self.makePresence(ptype='unsubscribed', pto=presence['from'].bare))
elif self.auto_authorize == None:
pass

View file

@ -1,5 +1,8 @@
from .. xmlstream.stanzabase import StanzaBase from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from . error import Error
from .. xmlstream.handler.waiter import Waiter
from .. xmlstream.matcher.id import MatcherId
class Iq(StanzaBase): class Iq(StanzaBase):
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject'))
@ -11,7 +14,6 @@ class Iq(StanzaBase):
StanzaBase.__init__(self, *args, **kwargs) StanzaBase.__init__(self, *args, **kwargs)
if self['id'] == '': if self['id'] == '':
self['id'] = self.stream.getId() self['id'] = self.stream.getId()
print("________LOADED IQ CLASS")
def result(self): def result(self):
self['type'] = 'result' self['type'] = 'result'
@ -37,3 +39,19 @@ class Iq(StanzaBase):
def unhandled(self): def unhandled(self):
pass pass
# returned unhandled error # returned unhandled error
def exception(self, traceback=None):
pass
def send(self, block=True, timeout=10):
if block:
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
self.stream.registerHandler(waitfor)
StanzaBase.send(self)
return waitfor.wait(timeout)
else:
return StanzaBase.send(self)
Iq.plugin_attrib_map['error'] = Error
Iq.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error

View file

@ -1,5 +1,6 @@
from .. xmlstream.stanzabase import StanzaBase from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from . error import Error
class Message(StanzaBase): class Message(StanzaBase):
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject'))
@ -25,21 +26,5 @@ class Message(StanzaBase):
self['body'] = body self['body'] = body
return self return self
if __name__ == '__main__': Message.plugin_attrib_map['error'] = Error
m = Message() Message.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
m['to'] = 'me'
m['from'] = 'you'
m['type'] = 'chat'
m.reply()
m['body'] = 'Hello there!'
m['subject'] = 'whatever'
m['id'] = 'abc'
print(str(m))
print(m['body'])
print(m['subject'])
print(m['id'])
m['type'] = None
m['body'] = None
m['id'] = None
print(str(m))
print(m['type'])

View file

@ -1,21 +1,51 @@
from .. xmlstream.stanzabase import StanzaBase from .. xmlstream.stanzabase import StanzaBase
from .. xmlstream import xmlstream as xmlstreammod from xml.etree import cElementTree as ET
from .. xmlstream.matcher.xpath import MatchXPath from . error import Error
#_bases = [StanzaBase] + xmlstreammod.stanza_extensions.get('PresenceStanza', []) class Presence(StanzaBase):
interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority'))
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed'))
showtypes = set(('dnd', 'ffc', 'xa', 'away'))
sub_interfaces = set(('status', 'priority'))
name = 'presence'
namespace = 'jabber:client'
#class PresenceStanza(*_bases): def getShowElement(self):
class PresenceStanza(StanzaBase): return self.xml.find("{%s}show" % self.namespace)
def setType(self, value):
if value in self.types:
show = self.getShowElement()
if value in self.types:
if show is not None:
self.xml.remove(show)
self._setAttr('type', value)
elif value in self.showtypes:
if show is None:
show = ET.Element("{%s}show" % self.namespace)
show.text = value
return self
def setPriority(self, value):
self._setSubText('priority', str(value))
def __init__(self, stream, xml=None): def getPriority(self):
self.pfrom = '' p = self._getSubText('priority')
self.pto = '' if not p: p = 0
StanzaBase.__init__(self, stream, xml, xmlstreammod.stanza_extensions.get('PresenceStanza', [])) return int(p)
def getType(self):
out = self._getAttr('type')
if not out:
show = self.getShowElement()
if show is not None:
out = show.text
if not out or out is None:
out = 'available'
return out
def delType(self):
self.setType('available')
def fromXML(self, xml): Presence.plugin_attrib_map['error'] = Error
StanzaBase.fromXML(self, xml) Presence.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
self.pfrom = xml.get('from')
self.pto = xml.get('to')
self.ptype = xml.get('type')
stanzas = ({'stanza_class': PresenceStanza, 'matcher': MatchXPath('{jabber:client}presence'), 'root': True},)

View file

@ -1,6 +1,7 @@
from . import base from . import base
import queue import queue
import logging import logging
from .. stanzabase import StanzaBase
class Waiter(base.BaseHandler): class Waiter(base.BaseHandler):
@ -19,7 +20,7 @@ class Waiter(base.BaseHandler):
return self._payload.get(True, timeout) return self._payload.get(True, timeout)
except queue.Empty: except queue.Empty:
logging.warning("Timed out waiting for %s" % self.name) logging.warning("Timed out waiting for %s" % self.name)
return False return StanzaBase(stype='error')
def checkDelete(self): def checkDelete(self):
return True return True

View file

@ -13,33 +13,53 @@ class JID(object):
return self.jid.split('@', 1)[-1].split('/', 1)[0] return self.jid.split('@', 1)[-1].split('/', 1)[0]
elif name == 'full': elif name == 'full':
return self.jid return self.jid
elif name == 'bare':
return self.jid.split('/', 1)[0]
def __str__(self): def __str__(self):
return self.jid return self.jid
class StanzaBase(object): class ElementBase(object):
name = 'stanza' name = 'stanza'
plugin_attrib = 'plugin'
namespace = 'jabber:client' namespace = 'jabber:client'
interfaces = set(('type', 'to', 'from', 'id', 'payload')) interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple() sub_interfaces = tuple()
plugin_attrib_map = {}
plugin_tag_map = {}
def __init__(self, stream, xml=None, stype=None, sto=None, sfrom=None, sid=None): def __init__(self, xml=None, parent=None):
self.stream = stream self.parent = parent
self.xml = xml self.xml = xml
if xml is None: self.plugins = {}
self.xml = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) if not self.setup(xml) and len(self.plugin_attrib_map):
if stype is not None: for child in self.xml.getchildren():
self['type'] = stype if child.tag in self.plugin_tag_map:
if sto is not None: self.plugins[self.plugin_tag_map[child.tag].plugin_attrib] = self.plugin_tag_map[child.tag](xml=child, parent=self)
self['to'] = sto
if sfrom is not None:
self['from'] = sfrom
self.tag = "{%s}%s" % (self.stream.default_ns, self.name)
def match(self, xml): def match(self, xml):
return xml.tag == self.tag return xml.tag == self.tag
def setup(self, xml=None):
if self.xml is None:
self.xml = xml
if self.xml is None:
self.xml = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace})
if self.parent is not None:
self.parent.xml.append(self.xml)
return True #had to generate XML
else:
return False
def enable(self, attrib):
self.initPlugin(attrib)
return self
def initPlugin(self, attrib):
if attrib not in self.plugins:
self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self)
def __getitem__(self, attrib): def __getitem__(self, attrib):
if attrib in self.interfaces: if attrib in self.interfaces:
if hasattr(self, "get%s" % attrib.title()): if hasattr(self, "get%s" % attrib.title()):
@ -49,11 +69,14 @@ class StanzaBase(object):
return self._getSubText(attrib) return self._getSubText(attrib)
else: else:
return self._getAttr(attrib) return self._getAttr(attrib)
elif attrib in self.plugin_attrib_map:
self.initPlugin(attrib)
return self.plugins[attrib]
else: else:
return '' return ''
def __setitem__(self, attrib, value): def __setitem__(self, attrib, value):
if attrib.lower() in self.interfaces: if attrib in self.interfaces:
if value is not None: if value is not None:
if hasattr(self, "set%s" % attrib.title()): if hasattr(self, "set%s" % attrib.title()):
getattr(self, "set%s" % attrib.title())(value,) getattr(self, "set%s" % attrib.title())(value,)
@ -64,6 +87,9 @@ class StanzaBase(object):
self._setAttr(attrib, value) self._setAttr(attrib, value)
else: else:
self.__delitem__(attrib) self.__delitem__(attrib)
elif attrib in self.plugin_map:
self.initPlugin(attrib)
self.plugins[attrib].setValues(value)
return self return self
def __delitem__(self, attrib): def __delitem__(self, attrib):
@ -75,62 +101,18 @@ class StanzaBase(object):
return self._delSub(attrib) return self._delSub(attrib)
else: else:
self._delAttr(attrib) self._delAttr(attrib)
return self elif attrib in self.plugin_map:
if attrib in self.plugins:
def setType(self, value): del self.plugins[attrib]
if value in self.types:
if value is None and 'type' in self.xml.attrib:
del self.xml.attrib['type']
elif value is not None:
self.xml.attrib['type'] = value
else:
raise ValueError
return self
def getPayload(self):
return self.xml.getchildren()
def setPayload(self, value):
self.xml.append(value)
def delPayload(self):
self.clear()
def clear(self):
for child in self.xml.getchildren():
self.xml.remove(child)
def reply(self):
self['from'], self['to'] = self['to'], self['from']
self.clear()
return self return self
def error(self): def __eq__(self, other):
self['type'] = 'error' values = self.getValues()
for key in other:
if key not in values or values[key] != other[key]:
return False
return True
def getTo(self):
return JID(self._getAttr('to'))
def setTo(self, value):
return self._setAttr('to', str(value))
def getFrom(self):
return JID(self._getAttr('from'))
def setFrom(self, value):
return self._setAttr('from', str(value))
def getValues(self):
out = {}
for interface in self.interfaces:
out[interface] = self[interface]
return out
def setValues(self, attrib):
for interface in attrib:
if interface in self.interfaces:
self[interface] = attrib[interface]
def _setAttr(self, name, value): def _setAttr(self, name, value):
self.xml.attrib[name] = value self.xml.attrib[name] = value
@ -162,6 +144,87 @@ class StanzaBase(object):
if child.tag == "{%s}%s" % (self.namespace, name): if child.tag == "{%s}%s" % (self.namespace, name):
self.xml.remove(child) self.xml.remove(child)
def getValues(self):
out = {}
for interface in self.interfaces:
out[interface] = self[interface]
return out
def setValues(self, attrib):
for interface in attrib:
if interface in self.interfaces:
self[interface] = attrib[interface]
return self
def append(self, xml):
self.xml.append(xml)
return self
def __del__(self):
if self.parent is not None:
self.parent.xml.remove(self.xml)
class StanzaBase(ElementBase):
name = 'stanza'
namespace = 'jabber:client'
interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple()
def __init__(self, stream, xml=None, stype=None, sto=None, sfrom=None, sid=None):
self.stream = stream
self.namespace = stream.default_ns
ElementBase.__init__(self, xml)
if stype is not None:
self['type'] = stype
if sto is not None:
self['to'] = sto
if sfrom is not None:
self['from'] = sfrom
self.tag = "{%s}%s" % (self.stream.default_ns, self.name)
def setType(self, value):
if value in self.types:
self.xml.attrib['type'] = value
else:
raise ValueError
return self
def getPayload(self):
return self.xml.getchildren()
def setPayload(self, value):
self.xml.append(value)
def delPayload(self):
self.clear()
def clear(self):
for child in self.xml.getchildren():
self.xml.remove(child)
for plugin in self.plugins:
del self.plugins[plugin]
def reply(self):
self['from'], self['to'] = self['to'], self['from']
self.clear()
return self
def error(self):
self['type'] = 'error'
def getTo(self):
return JID(self._getAttr('to'))
def setTo(self, value):
return self._setAttr('to', str(value))
def getFrom(self):
return JID(self._getAttr('from'))
def setFrom(self, value):
return self._setAttr('from', str(value))
def unhandled(self): def unhandled(self):
pass pass
@ -226,15 +289,3 @@ class StanzaBase(object):
text[cc] = '&quot;' text[cc] = '&quot;'
cc += 1 cc += 1
return ''.join(text) return ''.join(text)
if __name__ == '__main__':
x = Stanza()
x['from'] = 'you'
x['to'] = 'me'
print(x['from'], x['to'])
x.reply()
print(x['from'], x['to'])
x['from'] = None
print(x['from'], x['to'])
print(str(x))