3 # JabberBot: A simple jabber/xmpp bot framework
4 # Copyright (c) 2007-2009 Thomas Perl <thpinfo.com>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 # Homepage: http://thpinfo.com/2007/python-jabberbot/
28 print >>sys
.stderr
, 'You need to install xmpppy from http://xmpppy.sf.net/.'
33 """A simple jabber/xmpp bot framework
35 This is a simple bot framework around the "xmpppy" framework.
36 Copyright (c) 2007-2009 Thomas Perl <thpinfo.com>
38 To use, subclass the "JabberBot" class and implement "bot_" methods
39 (or whatever you set the command_prefix to), like this:
41 class StupidEchoBot(JabberBot):
42 def bot_echo( self, mess, args):
43 "The command description goes here"
44 return 'You said: ' + args
46 def bot_subscribe( self, mess, args):
47 "HIDDEN (Authorize the presence subscription request)"
48 # The docstring for this command has "HIDDEN" in it, so
49 # the help output does not show this command.
51 self.conn.Roster.Authorize( f)
54 def unknown_command( self, mess, cmd, args):
55 "This optional method, if present, gets called if the
56 command is not recognized."
57 if args.split()[0].startswith( 'cheese'):
58 return 'Sorry, cheesy commands not available.'
60 # if we return None, the default 'unknown command' text will get printed.
63 username = 'jid@server.example.com'
64 password = 'mypassword'
66 bot = StupidEchoBot( username, password)
71 __author__
= 'Thomas Perl <thp@thpinfo.com>'
75 class JabberBot(object):
76 command_prefix
= 'bot_'
78 def __init__( self
, jid
, password
, res
= None):
79 """Initializes the jabber bot and sets up commands."""
80 self
.jid
= xmpp
.JID( jid
)
81 self
.password
= password
82 self
.res
= (res
or self
.__class
__.__name
__)
84 self
.__finished
= False
86 self
.commands
= { 'help': self
.help_callback
, }
87 for (name
, value
) in inspect
.getmembers( self
):
88 if inspect
.ismethod( value
) and name
.startswith( self
.command_prefix
):
89 self
.commands
[name
[len(self
.command_prefix
):]] = value
92 """Logging facility, can be overridden in subclasses to log to file, etc.."""
93 print '%s: %s' % ( self
.__class
__.__name
__, s
, )
97 conn
= xmpp
.Client( self
.jid
.getDomain(), debug
= [])
99 if not conn
.connect():
100 self
.log( 'unable to connect to server.')
103 if not conn
.auth( self
.jid
.getNode(), self
.password
, self
.res
):
104 self
.log( 'unable to authorize with server.')
107 conn
.RegisterHandler( 'message', self
.callback_message
)
108 conn
.sendInitPresence()
114 """Stop serving messages and exit.
116 I find it is handy for development to run the
117 jabberbot in a 'while true' loop in the shell, so
118 whenever I make a code change to the bot, I send
119 the 'reload' command, which I have mapped to call
120 self.quit(), and my shell script relaunches the
123 self
.__finished
= True
125 def send( self
, user
, text
, in_reply_to
= None):
126 """Sends a simple message to the specified user."""
127 mess
= xmpp
.Message( user
, text
)
129 mess
.setThread( in_reply_to
.getThread())
130 mess
.setType( in_reply_to
.getType())
132 self
.connect().send( mess
)
134 def callback_message( self
, conn
, mess
):
135 """Messages sent to the bot will arrive here. Command handling + routing is done in this function."""
136 text
= mess
.getBody()
138 # If a message format is not supported (eg. encrypted), txt will be None
143 command
, args
= text
.split(' ',1)
145 command
, args
= text
,''
147 cmd
= command
.lower()
149 if self
.commands
.has_key(cmd
):
150 reply
= self
.commands
[cmd
]( mess
, args
)
152 unk_str
= 'Unknown command: "%s". Type "help" for available commands.' % cmd
153 reply
= self
.unknown_command( mess
, cmd
, args
) or unk_str
155 self
.send( mess
.getFrom(), reply
, mess
)
157 def unknown_command( self
, mess
, cmd
, args
):
158 """Default handler for unknown commands
160 Override this method in derived class if you
161 want to trap some unrecognized commands. If
162 'cmd' is handled, you must return some non-false
163 value, else some helpful text will be sent back
168 def help_callback( self
, mess
, args
):
169 """Returns a help string listing available options. Automatically assigned to the "help" command."""
170 usage
= '\n'.join(sorted(['%s: %s' % (name
, command
.__doc
__ or '(undocumented)') for (name
, command
) in self
.commands
.items() if name
!= 'help' and (not command
.__doc
__ or not command
.__doc
__.startswith('HIDDEN'))]))
173 description
= self
.__doc
__.strip()
175 description
= 'Available commands:'
177 return '%s\n\n%s' % ( description
, usage
, )
179 def idle_proc( self
):
180 """This function will be called in the main loop."""
183 def serve_forever( self
, connect_callback
= None, disconnect_callback
= None):
184 """Connects to the server and handles messages."""
185 conn
= self
.connect()
187 self
.log('bot connected. serving forever.')
189 self
.log('could not connect to server - aborting.')
195 while not self
.__finished
:
199 except KeyboardInterrupt:
200 self
.log('bot stopped by user request. shutting down.')
203 if disconnect_callback
:
204 disconnect_callback()