Merge branch 'develop' of github.com:fritzy/SleekXMPP into roster

This commit is contained in:
Lance Stout 2010-11-18 00:44:51 -05:00
commit 58b95e4ae4
11 changed files with 318 additions and 21 deletions

View file

@ -105,6 +105,10 @@ if __name__ == '__main__':
logging.basicConfig(level=opts.loglevel, logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s') format='%(levelname)-8s %(message)s')
if None in [opts.jid, opts.password]:
optp.print_help()
sys.exit(1)
# Setup the EchoBot and register plugins. Note that while plugins may # Setup the EchoBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does # have interdependencies, the order in which you register them does
# not matter. # not matter.

186
examples/muc.py Executable file
View file

@ -0,0 +1,186 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import sys
import logging
import time
from optparse import OptionParser
import sleekxmpp
# Python versions before 3.0 do not use UTF-8 encoding
# by default. To ensure that Unicode is handled properly
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
class MUCBot(sleekxmpp.ClientXMPP):
"""
A simple SleekXMPP bot that will greets those
who enter the room, and acknowledge any messages
that mentions the bot's nickname.
"""
def __init__(self, jid, password, room, nick):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.room = room
self.nick = nick
# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use. We want to
# listen for this event so that we we can intialize
# our roster.
self.add_event_handler("session_start", self.start)
# The groupchat_message event is triggered whenever a message
# stanza is received from any chat room. If you also also
# register a handler for the 'message' event, MUC messages
# will be processed by both handlers.
self.add_event_handler("groupchat_message", self.muc_message)
# The groupchat_presence event is triggered whenever a
# presence stanza is received from any chat room, including
# any presences you send yourself. To limit event handling
# to a single room, use the events muc::room@server::presence,
# muc::room@server::got_online, or muc::room@server::got_offline.
self.add_event_handler("muc::%s::got_online" % self.room,
self.muc_online)
def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an intial
presence stanza.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.getRoster()
self.sendPresence()
self.plugin['xep_0045'].joinMUC(self.room,
self.nick,
# If a room password is needed, use:
# password=the_room_password,
wait=True)
def muc_message(self, msg):
"""
Process incoming message stanzas from any chat room. Be aware
that if you also have any handlers for the 'message' event,
message stanzas may be processed by both handlers, so check
the 'type' attribute when using a 'message' event handler.
Whenever the bot's nickname is mentioned, respond to
the message.
IMPORTANT: Always check that a message is not from yourself,
otherwise you will create an infinite loop responding
to your own messages.
This handler will reply to messages that mention
the bot's nickname.
Arguments:
msg -- The received message stanza. See the documentation
for stanza objects and the Message stanza to see
how it may be used.
"""
if msg['mucnick'] != self.nick and self.nick in msg['body']:
self.send_message(mto=msg['from'].bare,
mbody="I heard that, %s." % msg['mucnick'],
mtype='groupchat')
def muc_online(self, presence):
"""
Process a presence stanza from a chat room. In this case,
presences from users that have just come online are
handled by sending a welcome message that includes
the user's nickname and role in the room.
Arguments:
presence -- The received presence stanza. See the
documentation for the Presence stanza
to see how else it may be used.
"""
if presence['muc']['nick'] != self.nick:
self.send_message(mto=presence['from'].bare,
mbody="Hello, %s %s" % (presence['muc']['role'],
presence['muc']['nick']),
mtype='groupchat')
if __name__ == '__main__':
# Setup the command line arguments.
optp = OptionParser()
# Output verbosity options.
optp.add_option('-q', '--quiet', help='set logging to ERROR',
action='store_const', dest='loglevel',
const=logging.ERROR, default=logging.INFO)
optp.add_option('-d', '--debug', help='set logging to DEBUG',
action='store_const', dest='loglevel',
const=logging.DEBUG, default=logging.INFO)
optp.add_option('-v', '--verbose', help='set logging to COMM',
action='store_const', dest='loglevel',
const=5, default=logging.INFO)
# JID and password options.
optp.add_option("-j", "--jid", dest="jid",
help="JID to use")
optp.add_option("-p", "--password", dest="password",
help="password to use")
optp.add_option("-r", "--room", dest="room",
help="MUC room to join")
optp.add_option("-n", "--nick", dest="nick",
help="MUC nickname")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if None in [opts.jid, opts.password, opts.room, opts.nick]:
optp.print_help()
sys.exit(1)
# Setup the MUCBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = MUCBot(opts.jid, opts.password, opts.room, opts.nick)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0045') # Multi-User Chat
xmpp.register_plugin('xep_0199') # XMPP Ping
# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
# If you do not have the pydns library installed, you will need
# to manually specify the name of the server if it does not match
# the one in the JID. For example, to use Google Talk you would
# need to use:
#
# if xmpp.connect(('talk.google.com', 5222)):
# ...
xmpp.process(threaded=False)
print("Done")
else:
print("Unable to connect.")

View file

@ -383,7 +383,7 @@ class ClientXMPP(BaseXMPP):
self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns, self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns,
bind_ns)).text) bind_ns)).text)
self.bound = True self.bound = True
log.info("Node set to: %s" % self.boundjid.fulljid) log.info("Node set to: %s" % self.boundjid.full)
session_ns = 'urn:ietf:params:xml:ns:xmpp-session' session_ns = 'urn:ietf:params:xml:ns:xmpp-session'
if "{%s}session" % session_ns not in self.features or self.bindfail: if "{%s}session" % session_ns not in self.features or self.bindfail:
log.debug("Established Session") log.debug("Established Session")

View file

@ -8,7 +8,7 @@
from sleekxmpp.stanza import Error from sleekxmpp.stanza import Error
from sleekxmpp.stanza.rootstanza import RootStanza from sleekxmpp.stanza.rootstanza import RootStanza
from sleekxmpp.xmlstream import RESPONSE_TIMEOUT, StanzaBase, ET from sleekxmpp.xmlstream import StanzaBase, ET
from sleekxmpp.xmlstream.handler import Waiter from sleekxmpp.xmlstream.handler import Waiter
from sleekxmpp.xmlstream.matcher import MatcherId from sleekxmpp.xmlstream.matcher import MatcherId
@ -157,7 +157,7 @@ class Iq(RootStanza):
StanzaBase.reply(self) StanzaBase.reply(self)
return self return self
def send(self, block=True, timeout=RESPONSE_TIMEOUT): def send(self, block=True, timeout=None):
""" """
Send an <iq> stanza over the XML stream. Send an <iq> stanza over the XML stream.
@ -174,6 +174,8 @@ class Iq(RootStanza):
before exiting the send call if blocking is used. before exiting the send call if blocking is used.
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
""" """
if timeout is None:
timeout = self.stream.response_timeout
if block and self['type'] in ('get', 'set'): if block and self['type'] in ('get', 'set'):
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
self.stream.registerHandler(waitfor) self.stream.registerHandler(waitfor)

View file

@ -7,6 +7,7 @@
""" """
import socket import socket
import threading
try: try:
import queue import queue
except ImportError: except ImportError:
@ -40,6 +41,8 @@ class TestLiveSocket(object):
self.recv_buffer = [] self.recv_buffer = []
self.recv_queue = queue.Queue() self.recv_queue = queue.Queue()
self.send_queue = queue.Queue() self.send_queue = queue.Queue()
self.send_queue_lock = threading.Lock()
self.recv_queue_lock = threading.Lock()
self.is_live = True self.is_live = True
def __getattr__(self, name): def __getattr__(self, name):
@ -108,6 +111,7 @@ class TestLiveSocket(object):
Placeholders. Same as for socket.recv. Placeholders. Same as for socket.recv.
""" """
data = self.socket.recv(*args, **kwargs) data = self.socket.recv(*args, **kwargs)
with self.recv_queue_lock:
self.recv_queue.put(data) self.recv_queue.put(data)
return data return data
@ -120,6 +124,7 @@ class TestLiveSocket(object):
Arguments: Arguments:
data -- String value to write. data -- String value to write.
""" """
with self.send_queue_lock:
self.send_queue.put(data) self.send_queue.put(data)
self.socket.send(data) self.socket.send(data)
@ -143,3 +148,15 @@ class TestLiveSocket(object):
Placeholders, same as socket.recv() Placeholders, same as socket.recv()
""" """
return self.recv(*args, **kwargs) return self.recv(*args, **kwargs)
def clear(self):
"""
Empty the send queue, typically done once the session has started to
remove the feature negotiation and log in stanzas.
"""
with self.send_queue_lock:
for i in range(0, self.send_queue.qsize()):
self.send_queue.get(block=False)
with self.recv_queue_lock:
for i in range(0, self.recv_queue.qsize()):
self.recv_queue.get(block=False)

View file

@ -7,6 +7,10 @@
""" """
import unittest import unittest
try:
import Queue as queue
except:
import queue
import sleekxmpp import sleekxmpp
from sleekxmpp import ClientXMPP, ComponentXMPP from sleekxmpp import ClientXMPP, ComponentXMPP
@ -219,7 +223,10 @@ class SleekTest(unittest.TestCase):
"Stanza:\n%s" % str(stanza)) "Stanza:\n%s" % str(stanza))
else: else:
stanza_class = stanza.__class__ stanza_class = stanza.__class__
if isinstance(criteria, str):
xml = self.parse_xml(criteria) xml = self.parse_xml(criteria)
else:
xml = criteria.xml
# Ensure that top level namespaces are used, even if they # Ensure that top level namespaces are used, even if they
# were not provided. # were not provided.
@ -305,6 +312,10 @@ class SleekTest(unittest.TestCase):
else: else:
raise ValueError("Unknown XMPP connection mode.") raise ValueError("Unknown XMPP connection mode.")
# We will use this to wait for the session_start event
# for live connections.
skip_queue = queue.Queue()
if socket == 'mock': if socket == 'mock':
self.xmpp.set_socket(TestSocket()) self.xmpp.set_socket(TestSocket())
@ -319,6 +330,10 @@ class SleekTest(unittest.TestCase):
self.xmpp.socket.recv_data(header) self.xmpp.socket.recv_data(header)
elif socket == 'live': elif socket == 'live':
self.xmpp.socket_class = TestLiveSocket self.xmpp.socket_class = TestLiveSocket
def wait_for_session(x):
self.xmpp.socket.clear()
skip_queue.put('started')
self.xmpp.add_event_handler('session_start', wait_for_session)
self.xmpp.connect() self.xmpp.connect()
else: else:
raise ValueError("Unknown socket type.") raise ValueError("Unknown socket type.")
@ -326,10 +341,13 @@ class SleekTest(unittest.TestCase):
self.xmpp.register_plugins() self.xmpp.register_plugins()
self.xmpp.process(threaded=True) self.xmpp.process(threaded=True)
if skip: if skip:
if socket != 'live':
# Clear startup stanzas # Clear startup stanzas
self.xmpp.socket.next_sent(timeout=1) self.xmpp.socket.next_sent(timeout=1)
if mode == 'component': if mode == 'component':
self.xmpp.socket.next_sent(timeout=1) self.xmpp.socket.next_sent(timeout=1)
else:
skip_queue.get(block=True, timeout=10)
def make_header(self, sto='', def make_header(self, sto='',
sfrom='', sfrom='',
@ -599,11 +617,12 @@ class SleekTest(unittest.TestCase):
Defaults to the value of self.match_method. Defaults to the value of self.match_method.
""" """
sent = self.xmpp.socket.next_sent(timeout) sent = self.xmpp.socket.next_sent(timeout)
if isinstance(data, str): if sent is None:
xml = self.parse_xml(data) return False
xml = self.parse_xml(sent)
self.fix_namespaces(xml, 'jabber:client') self.fix_namespaces(xml, 'jabber:client')
data = self.xmpp._build_stanza(xml, 'jabber:client') sent = self.xmpp._build_stanza(xml, 'jabber:client')
self.check(data, sent, self.check(sent, data,
method=method, method=method,
defaults=defaults, defaults=defaults,
use_values=use_values) use_values=use_values)

View file

@ -12,7 +12,7 @@ try:
except ImportError: except ImportError:
import Queue as queue import Queue as queue
from sleekxmpp.xmlstream import StanzaBase, RESPONSE_TIMEOUT from sleekxmpp.xmlstream import StanzaBase
from sleekxmpp.xmlstream.handler.base import BaseHandler from sleekxmpp.xmlstream.handler.base import BaseHandler
@ -69,7 +69,7 @@ class Waiter(BaseHandler):
""" """
pass pass
def wait(self, timeout=RESPONSE_TIMEOUT): def wait(self, timeout=None):
""" """
Block an event handler while waiting for a stanza to arrive. Block an event handler while waiting for a stanza to arrive.
@ -84,6 +84,9 @@ class Waiter(BaseHandler):
arrive. Defaults to the global default timeout arrive. Defaults to the global default timeout
value sleekxmpp.xmlstream.RESPONSE_TIMEOUT. value sleekxmpp.xmlstream.RESPONSE_TIMEOUT.
""" """
if timeout is None:
timeout = self.stream.response_timeout
try: try:
stanza = self._payload.get(True, timeout) stanza = self._payload.get(True, timeout)
except queue.Empty: except queue.Empty:

View file

@ -121,3 +121,6 @@ class JID(object):
def __str__(self): def __str__(self):
"""Use the full JID as the string value.""" """Use the full JID as the string value."""
return self.full return self.full
def __repr__(self):
return str(self)

View file

@ -25,6 +25,8 @@ except ImportError:
from sleekxmpp.thirdparty.statemachine import StateMachine from sleekxmpp.thirdparty.statemachine import StateMachine
from sleekxmpp.xmlstream import Scheduler, tostring from sleekxmpp.xmlstream import Scheduler, tostring
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
from sleekxmpp.xmlstream.handler import Waiter, XMLCallback
from sleekxmpp.xmlstream.matcher import MatchXMLMask
# In Python 2.x, file socket objects are broken. A patched socket # In Python 2.x, file socket objects are broken. A patched socket
# wrapper is provided for this case in filesocket.py. # wrapper is provided for this case in filesocket.py.
@ -162,6 +164,8 @@ class XMLStream(object):
self.ssl_support = SSL_SUPPORT self.ssl_support = SSL_SUPPORT
self.ssl_version = ssl.PROTOCOL_TLSv1 self.ssl_version = ssl.PROTOCOL_TLSv1
self.response_timeout = RESPONSE_TIMEOUT
self.state = StateMachine(('disconnected', 'connected')) self.state = StateMachine(('disconnected', 'connected'))
self.state._set_state('disconnected') self.state._set_state('disconnected')
@ -458,8 +462,6 @@ class XMLStream(object):
""" """
# To prevent circular dependencies, we must load the matcher # To prevent circular dependencies, we must load the matcher
# and handler classes here. # and handler classes here.
from sleekxmpp.xmlstream.matcher import MatchXMLMask
from sleekxmpp.xmlstream.handler import XMLCallback
if name is None: if name is None:
name = 'add_handler_%s' % self.getNewId() name = 'add_handler_%s' % self.getNewId()
@ -606,7 +608,7 @@ class XMLStream(object):
""" """
return xml return xml
def send(self, data, mask=None, timeout=RESPONSE_TIMEOUT): def send(self, data, mask=None, timeout=None):
""" """
A wrapper for send_raw for sending stanza objects. A wrapper for send_raw for sending stanza objects.
@ -621,6 +623,9 @@ class XMLStream(object):
timeout -- Time in seconds to wait for a response before timeout -- Time in seconds to wait for a response before
continuing. Defaults to RESPONSE_TIMEOUT. continuing. Defaults to RESPONSE_TIMEOUT.
""" """
if timeout is None:
timeout = self.response_timeout
if hasattr(mask, 'xml'): if hasattr(mask, 'xml'):
mask = mask.xml mask = mask.xml
data = str(data) data = str(data)
@ -643,7 +648,7 @@ class XMLStream(object):
self.send_queue.put(data) self.send_queue.put(data)
return True return True
def send_xml(self, data, mask=None, timeout=RESPONSE_TIMEOUT): def send_xml(self, data, mask=None, timeout=None):
""" """
Send an XML object on the stream, and optionally wait Send an XML object on the stream, and optionally wait
for a response. for a response.
@ -657,6 +662,8 @@ class XMLStream(object):
timeout -- Time in seconds to wait for a response before timeout -- Time in seconds to wait for a response before
continuing. Defaults to RESPONSE_TIMEOUT. continuing. Defaults to RESPONSE_TIMEOUT.
""" """
if timeout is None:
timeout = self.response_timeout
return self.send(tostring(data), mask, timeout) return self.send(tostring(data), mask, timeout)
def process(self, threaded=True): def process(self, threaded=True):

View file

@ -0,0 +1,57 @@
import logging
from sleekxmpp.test import *
class TestMultipleStreams(SleekTest):
"""
Test that we can test a live stanza stream.
"""
def setUp(self):
self.client1 = SleekTest()
self.client2 = SleekTest()
def tearDown(self):
self.client1.stream_close()
self.client2.stream_close()
def testMultipleStreams(self):
"""Test that we can interact with multiple live ClientXMPP instance."""
client1 = self.client1
client2 = self.client2
client1.stream_start(mode='client',
socket='live',
skip=True,
jid='user@localhost/test1',
password='user')
client2.stream_start(mode='client',
socket='live',
skip=True,
jid='user@localhost/test2',
password='user')
client1.xmpp.send_message(mto='user@localhost/test2',
mbody='test')
client1.send('message@body=test', method='stanzapath')
client2.recv('message@body=test', method='stanzapath')
suite = unittest.TestLoader().loadTestsFromTestCase(TestMultipleStreams)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG,
format='%(levelname)-8s %(message)s')
tests = unittest.TestSuite([suite])
result = unittest.TextTestRunner(verbosity=2).run(tests)
test_ns = 'http://andyet.net/protocol/tests'
print("<tests xmlns='%s' %s %s %s %s />" % (
test_ns,
'ran="%s"' % result.testsRun,
'errors="%s"' % len(result.errors),
'fails="%s"' % len(result.failures),
'success="%s"' % result.wasSuccessful()))

View file

@ -1,7 +1,6 @@
import logging import logging
from sleekxmpp.test import * from sleekxmpp.test import *
import sleekxmpp.plugins.xep_0033 as xep_0033
class TestLiveStream(SleekTest): class TestLiveStream(SleekTest):