comparison bot.py @ 0:4c842d23d4ce

Initial commit, version 0.1 Signed-off-by: Charly COSTE <changaco@changaco.net>
author Charly COSTE <changaco@changaco.net>
date Sun, 16 Aug 2009 01:47:03 +0200
parents
children f2d0a8b448db
comparison
equal deleted inserted replaced
-1:000000000000 0:4c842d23d4ce
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
18 # *** CONTRIBUTORS ***
19 # Contributor: Changaco <changaco@changaco.net>
20
21
22 # *** Changelog ***
23 # 0.1: First release
24
25
26 # *** Versioning ***
27 # Major will pass to 1 when xib will be considered fault-tolerant
28 # After that major will only be changed if the new version is not retro-compatible (e.g. requires changes in config file)
29
30 version = 0, 1
31
32
33 import irclib
34 import xmppony as xmpp
35 from threading import Thread
36 from bridge import *
37 from time import sleep
38 import re
39 import sys
40
41
42 class bot(Thread):
43
44 def __init__(self, jid, password, nickname, error_fd=sys.stderr, debug=False):
45 Thread.__init__(self)
46 self.jid = xmpp.protocol.JID(jid=jid)
47 self.nickname = nickname
48 self.password = password
49 self.error_fd = error_fd
50 self.debug = debug
51 self.bridges = []
52 self.irc = irclib.IRC()
53 self.irc.add_global_handler('all_events', self._irc_event_handler)
54 self.irc_thread = Thread(target=self.irc.process_forever)
55 self.irc_thread.start()
56 # Open connection with XMPP server
57 try:
58 self.xmpp_c = xmpp.client.Client(self.jid.getDomain(), debug=[])
59 self.xmpp_c.connect()
60 if self.jid.getResource() == '':
61 self.jid.setResource('xib-bot')
62 self.xmpp_c.auth(self.jid.getNode(), password, resource=self.jid.getResource())
63 self.xmpp_c.RegisterHandler('presence', self._xmpp_presence_handler)
64 self.xmpp_c.RegisterHandler('iq', self._xmpp_iq_handler)
65 self.xmpp_c.RegisterHandler('message', self._xmpp_message_handler)
66 self.xmpp_c.sendInitPresence()
67 except:
68 self.error('Error: XMPP Connection failed')
69 raise
70 self.xmpp_thread = Thread(target=self._xmpp_loop)
71 self.xmpp_thread.start()
72
73
74 def error(self, s, debug=False):
75 if not debug or debug and self.debug:
76 try:
77 self.error_fd.write(auto_encode(s)+"\n")
78 except EncodingException:
79 self.error_fd.write('Error message cannot be transcoded.\n')
80
81
82 def _xmpp_loop(self):
83 while True:
84 self.xmpp_c.Process(5)
85
86
87 def _xmpp_presence_handler(self, xmpp_c, presence):
88 """[Internal] Manage XMPP presence."""
89 self.error('==> Debug: Received XMPP presence.', debug=True)
90 self.error(presence.__str__(fancy=1), debug=True)
91
92 if presence.getTo() != self.jid:
93 #self.error('=> Debug: Skipping XMPP presence not received on bot connection.', debug=True)
94 return
95 from_ = xmpp.protocol.JID(presence.getFrom())
96 bare_jid = unicode(from_.getNode()+'@'+from_.getDomain())
97 for bridge in self.bridges:
98 if bare_jid == bridge.xmpp_room.room_jid:
99 # presence comes from a muc
100 resource = unicode(from_.getResource())
101 if resource == '':
102 # presence comes from the muc itself
103 # TODO: handle room deletion and muc server reboot
104 pass
105 else:
106 # presence comes from a participant of the muc
107 try:
108 p = bridge.getParticipant(resource)
109 if p.protocol == 'xmpp':
110 if presence.getType() == 'unavailable':
111 x = presence.getTag('x', namespace='http://jabber.org/protocol/muc#user')
112 if x and x.getTag('status', attrs={'code': '303'}):
113 # participant changed its nickname
114 item = x.getTag('item')
115 if not item:
116 self.error('Debug: bad stanza, no item element', debug=True)
117 return
118 new_nick = item.getAttr('nick')
119 if not new_nick:
120 self.error('Debug: bad stanza, new nick is not given', debug=True)
121 return
122 p.changeNickname(new_nick, 'irc')
123 return
124 # participant left
125 bridge.removeParticipant('xmpp', resource, presence.getStatus())
126 except NoSuchParticipantException:
127 try:
128 bridge.addParticipant('xmpp', resource)
129 except Exception:
130 pass
131 return
132
133
134 def _xmpp_iq_handler(self, xmpp_c, iq):
135 """[Internal] Manage XMPP IQs."""
136 self.error('=> Debug: Received XMPP iq.', debug=True)
137 self.error(iq.__str__(fancy=1), debug=True)
138
139
140 def _xmpp_message_handler(self, xmpp_c, message):
141 """[Internal] Manage XMPP messages."""
142 if message.getType() == 'chat':
143 self.error('==> Debug: Received XMPP message.', debug=True)
144 self.error(message.__str__(fancy=1), debug=True)
145 if message.getTo() == self.jid:
146 xmpp_c.send(xmpp.protocol.Message(to=message.getFrom(), body=u'Sorry I am a bot I don\'t speak …', typ='chat'))
147 else:
148 from_bare_jid = unicode(message.getFrom().getNode()+'@'+message.getFrom().getDomain())
149 for bridge in self.bridges:
150 if from_bare_jid == bridge.xmpp_room.room_jid:
151 # message comes from a room participant
152 try:
153 to_ = bridge.getParticipant(message.getTo().getResource())
154 from_ = bridge.getParticipant(message.getFrom().getResource())
155 except NoSuchParticipantException:
156 self.error('==> Debug: XMPP chat message not relayed, from_bare_jid='+from_bare_jid+' to='+str(message.getTo().getResource())+' from='+message.getFrom().getResource(), debug=True)
157 return
158 if from_.protocol in ['xmpp', 'both']:
159 from_.sayOnIRCTo(to_.nickname, message.getBody())
160 else:
161 self.error('==> Debug: received XMPP chat message from a non-XMPP participant, WTF ?', debug=True)
162
163 elif message.getType() == 'groupchat':
164 # message comes from a room
165 if message.getTo() != self.jid:
166 self.error('=> Debug: Skipping XMPP MUC message not received on bot connection.', debug=True)
167 return
168 for child in message.getChildren():
169 if child.getName() == 'delay':
170 self.error('=> Debug: Skipping XMPP MUC delayed message.', debug=True)
171 return
172 self.error('==> Debug: Received XMPP message.', debug=True)
173 self.error(message.__str__(fancy=1), debug=True)
174 from_ = xmpp.protocol.JID(message.getFrom())
175 room_jid = unicode(from_.getNode()+'@'+from_.getDomain())
176 for bridge in self.bridges:
177 if room_jid == bridge.xmpp_room.room_jid:
178 resource = unicode(from_.getResource())
179 if resource == '':
180 # message comes from the room itself
181 pass
182 else:
183 # message comes from a participant of the room
184 try:
185 participant_ = bridge.getParticipant(resource)
186 except NoSuchParticipantException:
187 return
188 if participant_.protocol == 'xmpp':
189 participant_.sayOnIRC(message.getBody())
190 elif participant_.protocol == 'both':
191 bridge.irc_connection.privmsg(bridge.irc_room, '<'+participant_.nickname+'> '+message.getBody())
192 else:
193 self.error('==> Debug: Received XMPP message.', debug=True)
194 self.error(message.__str__(fancy=1), debug=True)
195
196
197 def _irc_event_handler(self, connection, event):
198 """[internal] Manage IRC events"""
199 if not connection.bridge in self.bridges:
200 # Not for us, ignore
201 return
202 if 'all' in event.eventtype():
203 return
204 if 'motd' in event.eventtype():
205 self.error('=> Debug: ignoring event containing "motd" in the eventtype ('+event.eventtype()+')', debug=True)
206 return
207 if event.eventtype() in ['pong', 'welcome', 'yourhost', 'created', 'myinfo', 'featurelist', 'luserclient', 'luserop', 'luserchannels', 'luserme', 'n_local', 'n_global', 'endofnames', 'luserunknown']:
208 self.error('=> Debug: ignoring '+event.eventtype(), debug=True)
209 return
210 if event.eventtype() == 'pubmsg' and connection.get_nickname() != connection.bridge.irc_connection.get_nickname():
211 self.error('=> Debug: ignoring IRC pubmsg not received on bridge connection', debug=True)
212 return
213 if event.eventtype() == 'ping':
214 connection.pong(connection.get_server_name())
215 return
216 self.error('==> Debug: Received IRC event.', debug=True)
217 self.error('server='+connection.get_server_name(), debug=True)
218 self.error('eventtype='+event.eventtype(), debug=True)
219 self.error('source='+str(event.source()), debug=True)
220 self.error('target='+str(event.target()), debug=True)
221 self.error('arguments='+str(event.arguments()), debug=True)
222 if event.eventtype() == 'disconnect':
223 if connection.get_nickname() == connection.bridge.irc_connection.get_nickname():
224 # Lost bridge IRC connection, we must reconnect if we want the bridge to work
225 self.recreate_bridge(connection.bridge)
226 return
227 if connection.bridge.mode == 'normal' and event.arguments()[0] != 'Closing object':
228 connection.bridge.switchToLimitedMode()
229 elif event.eventtype() == 'nicknameinuse':
230 if connection.nick_callback:
231 connection.nick_callback('nicknameinuse')
232 else:
233 self.error('=> Debug: no nick callback for "'+str(event.target())+'"', debug=True)
234 return
235 elif event.eventtype() == 'erroneusnickname':
236 if connection.nick_callback:
237 connection.nick_callback('erroneusnickname')
238 else:
239 self.error('=> Debug: no nick callback for "'+str(event.target())+'"', debug=True)
240 return
241 elif event.eventtype() == 'umode':
242 if connection.nick_callback:
243 connection.nick_callback(None)
244 else:
245 self.error('=> Debug: no nick callback for "'+str(event.target())+'"', debug=True)
246 self.error('connection.nick_callback='+str(connection.nick_callback), debug=True)
247 return
248 elif event.eventtype() == 'namreply':
249 for nickname in re.split(' [@]?', event.arguments()[2].strip()):
250 try:
251 connection.bridge.addParticipant('irc', nickname)
252 except:
253 pass
254 return
255 elif event.eventtype() == 'join':
256 nickname = event.source().split('!')[0]
257 if nickname == self.nickname:
258 pass
259 else:
260 try:
261 connection.bridge.getParticipant(nickname)
262 except NoSuchParticipantException:
263 connection.bridge.addParticipant('irc', nickname)
264 try:
265 from_ = connection.bridge.getParticipant(event.source().split('!')[0])
266 if event.eventtype() == 'quit':
267 if from_.protocol == 'irc':
268 connection.bridge.removeParticipant('irc', from_.nickname, event.arguments()[0])
269 return
270 except NoSuchParticipantException:
271 return
272 except AttributeError:
273 pass
274 if event.eventtype() == 'pubmsg':
275 if from_.protocol == 'irc' or from_.protocol == 'both':
276 from_.sayOnXMPP(event.arguments()[0])
277 elif event.eventtype() == 'privmsg':
278 if event.target() == None:
279 return
280 try:
281 to_ = connection.bridge.getParticipant(event.target().split('!')[0])
282 except NoSuchParticipantException:
283 if event.target().split('!')[0] == self.nickname:
284 connection.privmsg(from_.nickname, u'Sorry I am a bot I don\'t speak …')
285 return
286 if to_.protocol == 'xmpp':
287 from_.sayOnXMPPTo(to_.nickname, event.arguments()[0])
288
289
290 def new_bridge(self, xmpp_room, irc_room, irc_server, irc_port=6667):
291 """Create a bridge between xmpp_room and irc_room at irc_server."""
292 b = bridge(self, xmpp_room, irc_room, irc_server, irc_port=irc_port)
293 self.bridges.append(b)
294 return b
295
296
297 def recreate_bridge(self, bridge):
298 """Disconnect and reconnect."""
299 self.new_bridge(bridge.xmpp_room.room_jid, bridge.irc_room, bridge.irc_server)
300 self.bridges.remove(bridge)
301 del bridge
302
303
304 def __del__(self):
305 for bridge in bridges:
306 del bridge