-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxmppbot.py
209 lines (162 loc) · 7.07 KB
/
xmppbot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/python
# JabberBot: A simple jabber/xmpp bot framework
# Copyright (c) 2007-2009 Thomas Perl <thpinfo.com>
# Copyright (c) 2009 Alex Gusev <alx>
# 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
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import sys
from presence_storage import MemoryPresenceStorage
from message_parsers import EchoParser
try:
import xmpp
except ImportError:
print >>sys.stderr, 'You need to install xmpppy from http://xmpppy.sf.net/.'
sys.exit(-1)
import inspect
"""A simple jabber/xmpp bot framework
This is a simple bot framework around the "xmpppy" framework.
Copyright (c) 2007-2009 Thomas Perl <thpinfo.com>
To use, subclass the "JabberBot" class and implement "bot_" methods
(or whatever you set the command_prefix to), like this:
class StupidEchoBot(JabberBot):
def bot_echo( self, mess, args):
"The command description goes here"
return 'You said: ' + args
def bot_subscribe( self, mess, args):
"HIDDEN (Authorize the presence subscription request)"
# The docstring for this command has "HIDDEN" in it, so
# the help output does not show this command.
f = mess.getFrom()
self.conn.Roster.Authorize( f)
return 'Authorized.'
def unknown_command( self, mess, cmd, args):
"This optional method, if present, gets called if the
command is not recognized."
if args.split()[0].startswith( 'cheese'):
return 'Sorry, cheesy commands not available.'
else:
# if we return None, the default 'unknown command' text will get printed.
return None
username = '[email protected]'
password = 'mypassword'
bot = StupidEchoBot( username, password)
bot.serve_forever()
"""
__author__ = 'Thomas Perl <[email protected]> and Alex Gusev <[email protected]>'
__version__ = '0.6'
class XMPPBot(object):
presence_storage = MemoryPresenceStorage()
message_parsers = [EchoParser(),
]
def __init__( self, jid, password, res = None):
"""Initializes the jabber bot and sets up commands."""
self.jid = xmpp.JID( jid)
self.password = password
self.res = (res or self.__class__.__name__)
self.conn = None
self.__finished = False
def log( self, s):
"""Logging facility, can be overridden in subclasses to log to file, etc.."""
print '%s: %s' % ( self.__class__.__name__, s, )
def connect( self):
if not self.conn:
conn = xmpp.Client( self.jid.getDomain(), debug = [])
if not conn.connect():
self.log( 'unable to connect to server.')
return None
if not conn.auth( self.jid.getNode(), self.password, self.res):
self.log( 'unable to authorize with server.')
return None
conn.RegisterHandler('message', self.message_handler)
conn.RegisterHandler('presence', self.presence_handler)
conn.RegisterCycleHandler(self.cycle_handler)
conn.sendInitPresence()
self.conn = conn
return self.conn
def quit( self):
"""Stop serving messages and exit.
I find it is handy for development to run the
jabberbot in a 'while true' loop in the shell, so
whenever I make a code change to the bot, I send
the 'reload' command, which I have mapped to call
self.quit(), and my shell script relaunches the
new version.
"""
self.__finished = True
def send( self, user, text, in_reply_to = None):
"""Sends a simple message to the specified user."""
mess = xmpp.Message( user, text)
#if html:
# mess.addChild('html', {}, payload=["markuped" + html], namespace='http://jabber.org/protocol/xhtml-im')
if in_reply_to:
mess.setThread( in_reply_to.getThread())
mess.setType( in_reply_to.getType())
try:
self.connect().send(mess)
except IOError:
self.connect()
def populate_message(self, mess):
""" Manipulate message before its being parsed"""
return mess
def message_handler(self, conn, mess):
"""Messages sent to the bot will arrive here. Command handling + routing is done in this function."""
reply = None
mess = self.populate_message(mess)
for message_parser in self.message_parsers:
if message_parser.recognize(mess):
reply = message_parser.process(mess)
break
if reply:
self.send(mess.getFrom(), reply, mess)
def presence_handler(self, conn, mess):
XMPPBot.presence_storage.set_presence(mess.getFrom(), mess.getShow(), mess.getStatus())
def unknown_command( self, mess, cmd, args):
"""Default handler for unknown commands
Override this method in derived class if you
want to trap some unrecognized commands. If
'cmd' is handled, you must return some non-false
value, else some helpful text will be sent back
to the sender.
"""
return None
def help_callback( self, mess, args):
"""Returns a help string listing available options. Automatically assigned to the "help" command."""
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'))]))
if self.__doc__:
description = self.__doc__.strip()
else:
description = 'Available commands:'
return '%s\n\n%s' % ( description, usage, )
def cycle_handler(self, dispatcher):
"""This function will be called in the main loop."""
#print dispatcher.__class__
pass
def serve_forever( self, connect_callback = None, disconnect_callback = None):
"""Connects to the server and handles messages."""
conn = self.connect()
if conn:
self.log('bot connected. serving forever.')
else:
self.log('could not connect to server - aborting.')
return
if connect_callback:
connect_callback()
while not self.__finished:
try:
conn.Process(0.01)
except KeyboardInterrupt:
self.log('bot stopped by user request. shutting down.')
break
if disconnect_callback:
disconnect_callback()