diff -Nru jabberbot-0.13/AUTHORS jabberbot-0.14/AUTHORS --- jabberbot-0.13/AUTHORS 2011-03-21 10:09:21.000000000 +0000 +++ jabberbot-0.14/AUTHORS 2011-07-31 10:01:39.000000000 +0000 @@ -11,4 +11,6 @@ Matthew Kemp David O'Rourke René Mayrhofer +Andreas Zwinkau +Fabian Deutsch diff -Nru jabberbot-0.13/debian/changelog jabberbot-0.14/debian/changelog --- jabberbot-0.13/debian/changelog 2011-05-08 14:45:53.000000000 +0000 +++ jabberbot-0.14/debian/changelog 2011-12-07 18:56:53.000000000 +0000 @@ -1,9 +1,10 @@ -jabberbot (0.13-2) unstable; urgency=low +jabberbot (0.14-1) unstable; urgency=low - * Team upload. - * Rebuild to add Python 2.7 support + * New upstream release (Closes: #643995) + * debian/control + - Bump the Standards-Version field to 3.9.2.0 - -- Piotr Ożarowski Sun, 08 May 2011 16:45:53 +0200 + -- Carl Chenet Wed, 07 Dec 2011 00:37:56 +0100 jabberbot (0.13-1) unstable; urgency=low diff -Nru jabberbot-0.13/debian/control jabberbot-0.14/debian/control --- jabberbot-0.13/debian/control 2011-04-06 17:34:39.000000000 +0000 +++ jabberbot-0.14/debian/control 2011-12-07 18:56:53.000000000 +0000 @@ -5,7 +5,7 @@ Maintainer: Debian Python Modules Team Uploaders: Carl Chenet Build-Depends: debhelper (>= 7.0.50~), python(>= 2.6.6-3), python-xmpp -Standards-Version: 3.9.1.0 +Standards-Version: 3.9.2.0 Homepage: http://thpinfo.com/2007/python-jabberbot/ Vcs-Svn: svn://svn.debian.org/svn/python-modules/packages/jabberbot/trunk/ Vcs-Browser: http://svn.debian.org/wsvn/python-modules/packages/jabberbot/trunk/ diff -Nru jabberbot-0.13/jabberbot.py jabberbot-0.14/jabberbot.py --- jabberbot-0.13/jabberbot.py 2011-04-04 15:06:56.000000000 +0000 +++ jabberbot-0.14/jabberbot.py 2011-07-31 10:18:11.000000000 +0000 @@ -3,6 +3,7 @@ # JabberBot: A simple jabber/xmpp bot framework # Copyright (c) 2007-2011 Thomas Perl +# $Id: 40d08966900594ddefb8d3302290c2be097ab2b8 $ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,16 +19,27 @@ # along with this program. If not, see . # -"""A simple framework for creating Jabber/XMPP bots and services""" +""" +A framework for writing Jabber/XMPP bots and services + +The JabberBot framework allows you to easily write bots +that use the XMPP protocol. You can create commands by +decorating functions in your subclass or customize the +bot's operation completely. MUCs are also supported. +""" import os import re import sys +import cgi try: import xmpp except ImportError: - print >>sys.stderr, 'You need to install xmpppy from http://xmpppy.sf.net/.' + print >> sys.stderr, """ + You need to install xmpppy from http://xmpppy.sf.net/. + On Debian-based systems, install the python-xmpp package. + """ sys.exit(-1) import time @@ -35,10 +47,11 @@ import logging import traceback +# Will be parsed by setup.py to determine package metadata __author__ = 'Thomas Perl ' -__version__ = '0.13' +__version__ = '0.14' __website__ = 'http://thp.io/2007/python-jabberbot/' -__license__ = 'GPLv3 or later' +__license__ = 'GNU General Public License version 3 or later' def botcmd(*args, **kwargs): """Decorator for bot command functions""" @@ -60,6 +73,10 @@ MSG_AUTHORIZE_ME = 'Hey there. You are not yet on my roster. Authorize my request and I will do the same.' MSG_NOT_AUTHORIZED = 'You did not authorize my subscription request. Access denied.' + MSG_UNKNOWN_COMMAND = 'Unknown command: "%(command)s". Type "help" for available commands.' + MSG_HELP_TAIL = 'Type help to get more info about that specific command.' + MSG_HELP_UNDEFINED_COMMAND = 'That command is not defined.' + MSG_ERROR_OCCURRED = 'Sorry for your inconvenience. An unexpected error occurred.' PING_FREQUENCY = 0 # Set to the number of seconds, e.g. 60. PING_TIMEOUT = 2 # Seconds to wait for a response. @@ -90,15 +107,17 @@ self.__status = None self.__seen = {} self.__threads = {} - self.__lastping = None + self.__lastping = time.time() self.__privatedomain = privatedomain self.__acceptownmsgs = acceptownmsgs + self.custom_message_handler = None + self.commands = {} for name, value in inspect.getmembers(self): if inspect.ismethod(value) and getattr(value, '_jabberbot_command', False): name = getattr(value, '_jabberbot_command_name') - self.log.debug('Registered command: %s' % name) + self.log.info('Registered command: %s' % name) self.commands[name] = value self.roster = None @@ -130,25 +149,25 @@ ################################ - def connect( self): + def connect(self): if not self.conn: if self.__debug: conn = xmpp.Client(self.jid.getDomain()) else: - conn = xmpp.Client(self.jid.getDomain(), debug = []) + conn = xmpp.Client(self.jid.getDomain(), debug=[]) conres = conn.connect() if not conres: self.log.error('unable to connect to server %s.' % self.jid.getDomain()) return None - if conres<>'tls': + if conres != 'tls': self.log.warning('unable to establish secure connection - TLS failed!') authres = conn.auth(self.jid.getNode(), self.__password, self.res) if not authres: self.log.error('unable to authorize with server.') return None - if authres<>'sasl': + if authres != 'sasl': self.log.warning("unable to perform SASL auth os %s. Old authentication method used!" % self.jid.getDomain()) conn.sendInitPresence() @@ -163,14 +182,18 @@ return self.conn - def join_room(self, room, username=None): + def join_room(self, room, username=None, password=None): """Join the specified multi-user chat room""" + NS_MUC = 'http://jabber.org/protocol/muc' if username is None: username = self.__username.split('@')[0] my_room_JID = '/'.join((room, username)) - self.connect().send(xmpp.Presence(to=my_room_JID)) + pres = xmpp.Presence(to=my_room_JID) + if password is not None: + pres.setTag('x',namespace=NS_MUC).setTagData('password',password) + self.connect().send(pres) - def quit( self): + def quit(self): """Stop serving messages and exit. I find it is handy for development to run the @@ -195,9 +218,9 @@ NS_TUNE = 'http://jabber.org/protocol/tune' iq = xmpp.Iq(typ='set') iq.setFrom(self.jid) - iq.pubsub = iq.addChild('pubsub', namespace = xmpp.NS_PUBSUB) - iq.pubsub.publish = iq.pubsub.addChild('publish', attrs = { 'node' : NS_TUNE }) - iq.pubsub.publish.item = iq.pubsub.publish.addChild('item', attrs= { 'id' : 'current' }) + iq.pubsub = iq.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + iq.pubsub.publish = iq.pubsub.addChild('publish', attrs={ 'node' : NS_TUNE }) + iq.pubsub.publish.item = iq.pubsub.publish.addChild('item', attrs={ 'id' : 'current' }) tune = iq.pubsub.publish.item.addChild('tune') tune.setNamespace(NS_TUNE) @@ -220,7 +243,7 @@ tune.addChild('uri').addData(song['uri']) if debug: - print 'Sending tune:', iq.__str__().encode('utf8') + self.log.info('Sending tune: %s' % iq.__str__().encode('utf8')) self.conn.send(iq) def send(self, user, text, in_reply_to=None, message_type='chat'): @@ -239,7 +262,7 @@ def send_simple_reply(self, mess, text, private=False): """Send a simple response to a message""" - self.send_message( self.build_reply(mess,text, private) ) + self.send_message(self.build_reply(mess, text, private)) def build_reply(self, mess, text=None, private=False): """Build a message for responding to another message. Message is NOT sent""" @@ -254,32 +277,44 @@ return response def build_message(self, text): - """Builds an xhtml message without attributes.""" - text_plain = re.sub(r'<[^>]+>', '', text) - message = xmpp.protocol.Message(body=text_plain) + """Builds an xhtml message without attributes. If input is not valid xhtml-im fallback to normal.""" + message = None # fixes local variable 'message' referenced before assignment - Thanks to Aleksandr + text_plain = re.sub(r'<[^>]+>', '', text) # Try to determine if text has xhtml-tags - TODO needs improvement if text_plain != text: - html = xmpp.Node('html', {'xmlns': 'http://jabber.org/protocol/xhtml-im'}) + message = xmpp.protocol.Message(body=text_plain) # Create body w stripped tags for reciptiens w/o xhtml-abilities - FIXME unescape " etc. + html = xmpp.Node('html', {'xmlns': 'http://jabber.org/protocol/xhtml-im'}) # Start creating a xhtml body try: html.addChild(node=xmpp.simplexml.XML2Node("" + text.encode('utf-8') + "")) message.addChild(node=html) except Exception, e: # Didn't work, incorrect markup or something. - # print >> sys.stderr, e, text - message = xmpp.protocol.Message(body=text_plain) + self.log.debug('An error while building a xhtml message. Fallback to normal messagebody') + message = None # Fallback - don't sanitize invalid input. User is responsible! + if message is None: + message = xmpp.protocol.Message(body=text) # Normal body return message def get_sender_username(self, mess): - """Extract the sender's user name from a message""" + """Extract the sender's user name from a message""" type = mess.getType() - jid = mess.getFrom() + jid = mess.getFrom() if type == "groupchat": username = jid.getResource() elif type == "chat": - username = jid.getNode() + username = jid.getNode() else: username = "" return username + def get_full_jids(self, jid): + """Returns all full jids, which belong to a bare jid + + Example: A bare jid is bob@jabber.org, with two clients connected, which + have the full jids bob@jabber.org/home and bob@jabber.org/work.""" + for res in self.roster.getResources(jid): + full_jid = "%s/%s" % (jid,res) + yield full_jid + def status_type_changed(self, jid, new_status_type): """Callback for tracking status types (available, away, offline, ...)""" self.log.debug('user %s changed status to %s' % (jid, new_status_type)) @@ -303,9 +338,17 @@ presence.getType(), presence.getShow(), \ presence.getStatus() - if self.jid.bareMatch(jid) and not self.__acceptownmsgs: - # Ignore our own presence messages - return + if self.jid.bareMatch(jid): + # update internal status + if type_ != self.OFFLINE: + self.__status = status + self.__show = show + else: + self.__status = "" + self.__show = self.OFFLINE + if not self.__acceptownmsgs: + # Ignore our own presence messages + return if type_ is None: # Keep track of status message and type changes @@ -370,33 +413,33 @@ self.send(jid, self.MSG_NOT_AUTHORIZED) self.roster.Unauthorize(jid) - def callback_message( self, conn, mess): + def callback_message(self, conn, mess): """Messages sent to the bot will arrive here. Command handling + routing is done in this function.""" self.__lastping = time.time() # Prepare to handle either private chats or group chats - type = mess.getType() - jid = mess.getFrom() - props = mess.getProperties() - text = mess.getBody() + type = mess.getType() + jid = mess.getFrom() + props = mess.getProperties() + text = mess.getBody() username = self.get_sender_username(mess) if type not in ("groupchat", "chat"): self.log.debug("unhandled message type: %s" % type) return + # Ignore messages from before we joined + if xmpp.NS_DELAY in props: return + + # Ignore messages from myself + if self.jid.bareMatch(jid): return + self.log.debug("*** props = %s" % props) self.log.debug("*** jid = %s" % jid) self.log.debug("*** username = %s" % username) self.log.debug("*** type = %s" % type) self.log.debug("*** text = %s" % text) - # Ignore messages from before we joined - if xmpp.NS_DELAY in props: return - - # Ignore messages from myself - if username == self.__username: return - # If a message format is not supported (eg. encrypted), txt will be None if not text: return @@ -416,23 +459,39 @@ cmd = command.lower() self.log.debug("*** cmd = %s" % cmd) - if self.commands.has_key(cmd): + if self.custom_message_handler is not None: + # Try the custom handler first. It can return None + # if you want JabberBot to fall back to the default. + reply = self.custom_message_handler(mess, text) + + # If your custom_message_handler returns True, it + # is assumed that the custom_message_handler has + # taken care of processing the message, so we do + # not process the message any further here. + if reply == True: + return + else: + reply = None + + if reply is None and self.commands.has_key(cmd): try: reply = self.commands[cmd](mess, args) except Exception, e: - reply = traceback.format_exc(e) - self.log.exception('An error happened while processing a message ("%s") from %s: %s"' % (text, jid, reply)) + self.log.exception('An error happened while processing a message ("%s") from %s: %s"' % (text, jid, traceback.format_exc(e))) + reply = self.MSG_ERROR_OCCURRED else: # In private chat, it's okay for the bot to always respond. # In group chat, the bot should silently ignore commands it # doesn't understand or aren't handled by unknown_command(). - default_reply = 'Unknown command: "%s". Type "help" for available commands.blubb!' % cmd - if type == "groupchat": default_reply = None - reply = self.unknown_command( mess, cmd, args) + if type == 'groupchat': + default_reply = None + else: + default_reply = self.MSG_UNKNOWN_COMMAND % {'command': cmd} + reply = self.unknown_command(mess, cmd, args) if reply is None: reply = default_reply if reply: - self.send_simple_reply(mess,reply) + self.send_simple_reply(mess, reply) def unknown_command(self, mess, cmd, args): """Default handler for unknown commands @@ -465,7 +524,7 @@ @botcmd def help(self, mess, args): - """Returns a help string listing available options. + """ Returns a help string listing available options. Automatically assigned to the "help" command.""" if not args: @@ -475,25 +534,22 @@ description = 'Available commands:' usage = '\n'.join(sorted([ - '%s: %s' % (name, (command.__doc__.strip() or '(undocumented)').split('\n', 1)[0]) + '%s: %s' % (name, (command.__doc__ or '(undocumented)').strip().split('\n', 1)[0]) for (name, command) in self.commands.iteritems() if name != 'help' and not command._jabberbot_hidden ])) - usage = usage + '\n\nType help to get more info about that specific command.' + usage = '\n\n'.join(filter(None, [usage, self.MSG_HELP_TAIL])) else: description = '' if args in self.commands: - usage = self.commands[args].__doc__.strip() or 'undocumented' + usage = (self.commands[args].__doc__ or 'undocumented').strip() else: - usage = 'That command is not defined.' + usage = self.MSG_HELP_UNDEFINED_COMMAND - top = self.top_of_help_message() + top = self.top_of_help_message() bottom = self.bottom_of_help_message() - if top : top = "%s\n\n" % top - if bottom: bottom = "\n\n%s" % bottom + return '\n\n'.join(filter(None, [top, description, usage, bottom])) - return '%s%s\n\n%s%s' % ( top, description, usage, bottom ) - - def idle_proc( self): + def idle_proc(self): """This function will be called in the main loop.""" self._idle_ping() @@ -505,7 +561,7 @@ if self.PING_FREQUENCY and time.time() - self.__lastping > self.PING_FREQUENCY: self.__lastping = time.time() #logging.debug('Pinging the server.') - ping = xmpp.Protocol('iq',typ='get',payload=[xmpp.Node('ping',attrs={'xmlns':'urn:xmpp:ping'})]) + ping = xmpp.Protocol('iq', typ='get', payload=[xmpp.Node('ping', attrs={'xmlns':'urn:xmpp:ping'})]) try: res = self.conn.SendAndWaitForResponse(ping, self.PING_TIMEOUT) #logging.debug('Got response: ' + str(res)) @@ -527,7 +583,7 @@ """ pass - def serve_forever( self, connect_callback = None, disconnect_callback = None): + def serve_forever(self, connect_callback=None, disconnect_callback=None): """Connects to the server and handles messages.""" conn = self.connect() if conn: @@ -553,4 +609,4 @@ if disconnect_callback: disconnect_callback() - +# vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 diff -Nru jabberbot-0.13/PKG-INFO jabberbot-0.14/PKG-INFO --- jabberbot-0.13/PKG-INFO 2011-04-04 15:09:59.000000000 +0000 +++ jabberbot-0.14/PKG-INFO 2011-07-31 10:18:56.000000000 +0000 @@ -1,11 +1,14 @@ Metadata-Version: 1.0 Name: jabberbot -Version: 0.13 -Summary: A simple framework for creating Jabber/XMPP bots and services +Version: 0.14 +Summary: A framework for writing Jabber/XMPP bots and services Home-page: http://thp.io/2007/python-jabberbot/ Author: Thomas Perl Author-email: m@thp.io -License: GPLv3 or later -Download-URL: http://thp.io/2007/python-jabberbot/jabberbot-0.13.tar.gz -Description: UNKNOWN +License: GNU General Public License version 3 or later +Download-URL: http://thp.io/2007/python-jabberbot/jabberbot-0.14.tar.gz +Description: The JabberBot framework allows you to easily write bots + that use the XMPP protocol. You can create commands by + decorating functions in your subclass or customize the + bot's operation completely. MUCs are also supported. Platform: UNKNOWN diff -Nru jabberbot-0.13/README jabberbot-0.14/README --- jabberbot-0.13/README 2011-02-28 15:20:10.000000000 +0000 +++ jabberbot-0.14/README 2011-07-31 10:12:14.000000000 +0000 @@ -7,7 +7,11 @@ this release can be found in the file AUTHORS. Example code that you can use to get started can be found in the "examples/" subdirectory. -You need to have the "xmpppy" module installed, as jabberbot depends on it. +Dependencies: + + * xmpppy (http://xmpppy.sf.net/) + On Debian-based systems, xmpppy is packaged as "python-xmpp" + [1] http://thp.io/2007/python-jabberbot/ [2] http://repo.or.cz/w/gpodder.git diff -Nru jabberbot-0.13/setup.py jabberbot-0.14/setup.py --- jabberbot-0.13/setup.py 2011-04-04 15:07:03.000000000 +0000 +++ jabberbot-0.14/setup.py 2011-07-31 10:15:28.000000000 +0000 @@ -1,38 +1,41 @@ #!/usr/bin/env python -# Setup script for python-jabberbot +# Generic setup script for single-module Python projects # by Thomas Perl from distutils.core import setup -import os import re -import sys -# Make sure that we import the local jabberbot module -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import jabberbot - - -# How is the package going to be called? PACKAGE = 'jabberbot' +SCRIPT_FILE = PACKAGE + '.py' + +main_py = open(SCRIPT_FILE).read() +metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py)) +docstrings = re.findall('"""(.*?)"""', main_py, re.DOTALL) # List the modules that need to be installed/packaged MODULES = ( - 'jabberbot', + PACKAGE, ) -# These metadata fields are simply taken from the Jabberbot module -VERSION = jabberbot.__version__ -WEBSITE = jabberbot.__website__ -LICENSE = jabberbot.__license__ -DESCRIPTION = jabberbot.__doc__ +# Metadata fields extracted from SCRIPT_FILE +AUTHOR_EMAIL = metadata['author'] +VERSION = metadata['version'] +WEBSITE = metadata['website'] +LICENSE = metadata['license'] +DESCRIPTION = docstrings[0].strip() +if '\n\n' in DESCRIPTION: + DESCRIPTION, LONG_DESCRIPTION = DESCRIPTION.split('\n\n', 1) +else: + LONG_DESCRIPTION = None # Extract name and e-mail ("Firstname Lastname ") -AUTHOR, EMAIL = re.match(r'(.*) <(.*)>', jabberbot.__author__).groups() +AUTHOR, EMAIL = re.match(r'(.*) <(.*)>', AUTHOR_EMAIL).groups() setup(name=PACKAGE, version=VERSION, description=DESCRIPTION, + long_description=LONG_DESCRIPTION, author=AUTHOR, author_email=EMAIL, license=LICENSE,