r86908 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86907‎ | r86908 | r86909 >
Date:21:52, 25 April 2011
Author:laner
Status:deferred
Tags:
Comment:
Removing ircbot.py and irclib.py, and adding a dependency for those libraries via the package.
Modified paths:
  • /trunk/debs/ircecho/debian/control (modified) (history)
  • /trunk/debs/ircecho/ircbot.py (deleted) (history)
  • /trunk/debs/ircecho/ircecho (modified) (history)
  • /trunk/debs/ircecho/irclib.py (deleted) (history)

Diff [purge]

Index: trunk/debs/ircecho/ircbot.py
@@ -1,432 +0,0 @@
2 -# Copyright (C) 1999--2002 Joel Rosdahl
3 -#
4 -# This library is free software; you can redistribute it and/or
5 -# modify it under the terms of the GNU Lesser General Public
6 -# License as published by the Free Software Foundation; either
7 -# version 2.1 of the License, or (at your option) any later version.
8 -#
9 -# This library 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 GNU
12 -# Lesser General Public License for more details.
13 -#
14 -# You should have received a copy of the GNU Lesser General Public
15 -# License along with this library; if not, write to the Free Software
16 -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 -#
18 -# Joel Rosdahl <joel@rosdahl.net>
19 -#
20 -# $Id: ircbot.py,v 1.14 2003/10/29 21:11:07 jrosdahl Exp $
21 -
22 -"""ircbot -- Simple IRC bot library.
23 -
24 -This module contains a single-server IRC bot class that can be used to
25 -write simpler bots.
26 -"""
27 -
28 -import sys
29 -import string
30 -from UserDict import UserDict
31 -
32 -from irclib import SimpleIRCClient
33 -from irclib import nm_to_n, irc_lower, all_events
34 -from irclib import parse_channel_modes, is_channel
35 -from irclib import ServerConnectionError
36 -
37 -class SingleServerIRCBot(SimpleIRCClient):
38 - """A single-server IRC bot class.
39 -
40 - The bot tries to reconnect if it is disconnected.
41 -
42 - The bot keeps track of the channels it has joined, the other
43 - clients that are present in the channels and which of those that
44 - have operator or voice modes. The "database" is kept in the
45 - self.channels attribute, which is an IRCDict of Channels.
46 - """
47 - def __init__(self, server_list, nickname, realname, reconnection_interval=60):
48 - """Constructor for SingleServerIRCBot objects.
49 -
50 - Arguments:
51 -
52 - server_list -- A list of tuples (server, port) that
53 - defines which servers the bot should try to
54 - connect to.
55 -
56 - nickname -- The bot's nickname.
57 -
58 - realname -- The bot's realname.
59 -
60 - reconnection_interval -- How long the bot should wait
61 - before trying to reconnect.
62 -
63 - dcc_connections -- A list of initiated/accepted DCC
64 - connections.
65 - """
66 -
67 - SimpleIRCClient.__init__(self)
68 - self.channels = IRCDict()
69 - self.server_list = server_list
70 - if not reconnection_interval or reconnection_interval < 0:
71 - reconnection_interval = 2**31
72 - self.reconnection_interval = reconnection_interval
73 -
74 - self._nickname = nickname
75 - self._realname = realname
76 - for i in ["disconnect", "join", "kick", "mode",
77 - "namreply", "nick", "part", "quit"]:
78 - self.connection.add_global_handler(i,
79 - getattr(self, "_on_" + i),
80 - -10)
81 - def _connected_checker(self):
82 - """[Internal]"""
83 - if not self.connection.is_connected():
84 - self.connection.execute_delayed(self.reconnection_interval,
85 - self._connected_checker)
86 - self.jump_server()
87 -
88 - def _connect(self):
89 - """[Internal]"""
90 - password = None
91 - if len(self.server_list[0]) > 2:
92 - password = self.server_list[0][2]
93 - try:
94 - self.connect(self.server_list[0][0],
95 - self.server_list[0][1],
96 - self._nickname,
97 - password,
98 - ircname=self._realname)
99 - except ServerConnectionError:
100 - pass
101 -
102 - def _on_disconnect(self, c, e):
103 - """[Internal]"""
104 - self.channels = IRCDict()
105 - self.connection.execute_delayed(self.reconnection_interval,
106 - self._connected_checker)
107 -
108 - def _on_join(self, c, e):
109 - """[Internal]"""
110 - ch = e.target()
111 - nick = nm_to_n(e.source())
112 - if nick == c.get_nickname():
113 - self.channels[ch] = Channel()
114 - self.channels[ch].add_user(nick)
115 -
116 - def _on_kick(self, c, e):
117 - """[Internal]"""
118 - nick = e.arguments()[0]
119 - channel = e.target()
120 -
121 - if nick == c.get_nickname():
122 - del self.channels[channel]
123 - else:
124 - self.channels[channel].remove_user(nick)
125 -
126 - def _on_mode(self, c, e):
127 - """[Internal]"""
128 - modes = parse_channel_modes(string.join(e.arguments()))
129 - t = e.target()
130 - if is_channel(t):
131 - ch = self.channels[t]
132 - for mode in modes:
133 - if mode[0] == "+":
134 - f = ch.set_mode
135 - else:
136 - f = ch.clear_mode
137 - f(mode[1], mode[2])
138 - else:
139 - # Mode on self... XXX
140 - pass
141 -
142 - def _on_namreply(self, c, e):
143 - """[Internal]"""
144 -
145 - # e.arguments()[0] == "=" (why?)
146 - # e.arguments()[1] == channel
147 - # e.arguments()[2] == nick list
148 -
149 - ch = e.arguments()[1]
150 - for nick in string.split(e.arguments()[2]):
151 - if nick[0] == "@":
152 - nick = nick[1:]
153 - self.channels[ch].set_mode("o", nick)
154 - elif nick[0] == "+":
155 - nick = nick[1:]
156 - self.channels[ch].set_mode("v", nick)
157 - self.channels[ch].add_user(nick)
158 -
159 - def _on_nick(self, c, e):
160 - """[Internal]"""
161 - before = nm_to_n(e.source())
162 - after = e.target()
163 - for ch in self.channels.values():
164 - if ch.has_user(before):
165 - ch.change_nick(before, after)
166 -
167 - def _on_part(self, c, e):
168 - """[Internal]"""
169 - nick = nm_to_n(e.source())
170 - channel = e.target()
171 -
172 - if nick == c.get_nickname():
173 - del self.channels[channel]
174 - else:
175 - self.channels[channel].remove_user(nick)
176 -
177 - def _on_quit(self, c, e):
178 - """[Internal]"""
179 - nick = nm_to_n(e.source())
180 - for ch in self.channels.values():
181 - if ch.has_user(nick):
182 - ch.remove_user(nick)
183 -
184 - def die(self, msg="Bye, cruel world!"):
185 - """Let the bot die.
186 -
187 - Arguments:
188 -
189 - msg -- Quit message.
190 - """
191 - self.connection.quit(msg)
192 - sys.exit(0)
193 -
194 - def disconnect(self, msg="I'll be back!"):
195 - """Disconnect the bot.
196 -
197 - The bot will try to reconnect after a while.
198 -
199 - Arguments:
200 -
201 - msg -- Quit message.
202 - """
203 - self.connection.quit(msg)
204 -
205 - def get_version(self):
206 - """Returns the bot version.
207 -
208 - Used when answering a CTCP VERSION request.
209 - """
210 - return "ircbot.py by Joel Rosdahl <joel@rosdahl.net>"
211 -
212 - def jump_server(self):
213 - """Connect to a new server, possibly disconnecting from the current.
214 -
215 - The bot will skip to next server in the server_list each time
216 - jump_server is called.
217 - """
218 - if self.connection.is_connected():
219 - self.connection.quit("Jumping servers")
220 - self.server_list.append(self.server_list.pop(0))
221 - self._connect()
222 -
223 - def on_ctcp(self, c, e):
224 - """Default handler for ctcp events.
225 -
226 - Replies to VERSION and PING requests and relays DCC requests
227 - to the on_dccchat method.
228 - """
229 - if e.arguments()[0] == "VERSION":
230 - c.ctcp_reply(nm_to_n(e.source()),
231 - "VERSION " + self.get_version())
232 - elif e.arguments()[0] == "PING":
233 - if len(e.arguments()) > 1:
234 - c.ctcp_reply(nm_to_n(e.source()),
235 - "PING " + e.arguments()[1])
236 - elif e.arguments()[0] == "DCC" and e.arguments()[1] == "CHAT":
237 - self.on_dccchat(c, e)
238 -
239 - def on_dccchat(self, c, e):
240 - pass
241 -
242 - def start(self):
243 - """Start the bot."""
244 - self._connect()
245 - SimpleIRCClient.start(self)
246 -
247 -
248 -class IRCDict:
249 - """A dictionary suitable for storing IRC-related things.
250 -
251 - Dictionary keys a and b are considered equal if and only if
252 - irc_lower(a) == irc_lower(b)
253 -
254 - Otherwise, it should behave exactly as a normal dictionary.
255 - """
256 -
257 - def __init__(self, dict=None):
258 - self.data = {}
259 - self.canon_keys = {} # Canonical keys
260 - if dict is not None:
261 - self.update(dict)
262 - def __repr__(self):
263 - return repr(self.data)
264 - def __cmp__(self, dict):
265 - if isinstance(dict, IRCDict):
266 - return cmp(self.data, dict.data)
267 - else:
268 - return cmp(self.data, dict)
269 - def __len__(self):
270 - return len(self.data)
271 - def __getitem__(self, key):
272 - return self.data[self.canon_keys[irc_lower(key)]]
273 - def __setitem__(self, key, item):
274 - if self.has_key(key):
275 - del self[key]
276 - self.data[key] = item
277 - self.canon_keys[irc_lower(key)] = key
278 - def __delitem__(self, key):
279 - ck = irc_lower(key)
280 - del self.data[self.canon_keys[ck]]
281 - del self.canon_keys[ck]
282 - def clear(self):
283 - self.data.clear()
284 - self.canon_keys.clear()
285 - def copy(self):
286 - if self.__class__ is UserDict:
287 - return UserDict(self.data)
288 - import copy
289 - return copy.copy(self)
290 - def keys(self):
291 - return self.data.keys()
292 - def items(self):
293 - return self.data.items()
294 - def values(self):
295 - return self.data.values()
296 - def has_key(self, key):
297 - return self.canon_keys.has_key(irc_lower(key))
298 - def update(self, dict):
299 - for k, v in dict.items():
300 - self.data[k] = v
301 - def get(self, key, failobj=None):
302 - return self.data.get(key, failobj)
303 -
304 -
305 -class Channel:
306 - """A class for keeping information about an IRC channel.
307 -
308 - This class can be improved a lot.
309 - """
310 -
311 - def __init__(self):
312 - self.userdict = IRCDict()
313 - self.operdict = IRCDict()
314 - self.voiceddict = IRCDict()
315 - self.modes = {}
316 -
317 - def users(self):
318 - """Returns an unsorted list of the channel's users."""
319 - return self.userdict.keys()
320 -
321 - def opers(self):
322 - """Returns an unsorted list of the channel's operators."""
323 - return self.operdict.keys()
324 -
325 - def voiced(self):
326 - """Returns an unsorted list of the persons that have voice
327 - mode set in the channel."""
328 - return self.voiceddict.keys()
329 -
330 - def has_user(self, nick):
331 - """Check whether the channel has a user."""
332 - return self.userdict.has_key(nick)
333 -
334 - def is_oper(self, nick):
335 - """Check whether a user has operator status in the channel."""
336 - return self.operdict.has_key(nick)
337 -
338 - def is_voiced(self, nick):
339 - """Check whether a user has voice mode set in the channel."""
340 - return self.voiceddict.has_key(nick)
341 -
342 - def add_user(self, nick):
343 - self.userdict[nick] = 1
344 -
345 - def remove_user(self, nick):
346 - for d in self.userdict, self.operdict, self.voiceddict:
347 - if d.has_key(nick):
348 - del d[nick]
349 -
350 - def change_nick(self, before, after):
351 - self.userdict[after] = 1
352 - del self.userdict[before]
353 - if self.operdict.has_key(before):
354 - self.operdict[after] = 1
355 - del self.operdict[before]
356 - if self.voiceddict.has_key(before):
357 - self.voiceddict[after] = 1
358 - del self.voiceddict[before]
359 -
360 - def set_mode(self, mode, value=None):
361 - """Set mode on the channel.
362 -
363 - Arguments:
364 -
365 - mode -- The mode (a single-character string).
366 -
367 - value -- Value
368 - """
369 - if mode == "o":
370 - self.operdict[value] = 1
371 - elif mode == "v":
372 - self.voiceddict[value] = 1
373 - else:
374 - self.modes[mode] = value
375 -
376 - def clear_mode(self, mode, value=None):
377 - """Clear mode on the channel.
378 -
379 - Arguments:
380 -
381 - mode -- The mode (a single-character string).
382 -
383 - value -- Value
384 - """
385 - try:
386 - if mode == "o":
387 - del self.operdict[value]
388 - elif mode == "v":
389 - del self.voiceddict[value]
390 - else:
391 - del self.modes[mode]
392 - except KeyError:
393 - pass
394 -
395 - def has_mode(self, mode):
396 - return mode in self.modes
397 -
398 - def is_moderated(self):
399 - return self.has_mode("m")
400 -
401 - def is_secret(self):
402 - return self.has_mode("s")
403 -
404 - def is_protected(self):
405 - return self.has_mode("p")
406 -
407 - def has_topic_lock(self):
408 - return self.has_mode("t")
409 -
410 - def is_invite_only(self):
411 - return self.has_mode("i")
412 -
413 - def has_message_from_outside_protection(self):
414 - # Eh... What should it be called, really?
415 - return self.has_mode("n")
416 -
417 - def has_limit(self):
418 - return self.has_mode("l")
419 -
420 - def limit(self):
421 - if self.has_limit():
422 - return self.modes[l]
423 - else:
424 - return None
425 -
426 - def has_key(self):
427 - return self.has_mode("k")
428 -
429 - def key(self):
430 - if self.has_key():
431 - return self.modes["k"]
432 - else:
433 - return None
Index: trunk/debs/ircecho/irclib.py
@@ -1,1554 +0,0 @@
2 -# Copyright (C) 1999--2002 Joel Rosdahl
3 -#
4 -# This library is free software; you can redistribute it and/or
5 -# modify it under the terms of the GNU Lesser General Public
6 -# License as published by the Free Software Foundation; either
7 -# version 2.1 of the License, or (at your option) any later version.
8 -#
9 -# This library 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 GNU
12 -# Lesser General Public License for more details.
13 -#
14 -# You should have received a copy of the GNU Lesser General Public
15 -# License along with this library; if not, write to the Free Software
16 -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 -#
18 -# Joel Rosdahl <joel@rosdahl.net>
19 -#
20 -# $Id: irclib.py,v 1.23 2004/07/09 08:54:40 jrosdahl Exp $
21 -
22 -"""irclib -- Internet Relay Chat (IRC) protocol client library.
23 -
24 -This library is intended to encapsulate the IRC protocol at a quite
25 -low level. It provides an event-driven IRC client framework. It has
26 -a fairly thorough support for the basic IRC protocol and CTCP, but DCC
27 -connection support is not yet implemented.
28 -
29 -In order to understand how to make an IRC client, I'm afraid you more
30 -or less must understand the IRC specifications. They are available
31 -here: [IRC specifications].
32 -
33 -The main features of the IRC client framework are:
34 -
35 - * Abstraction of the IRC protocol.
36 - * Handles multiple simultaneous IRC server connections.
37 - * Handles server PONGing transparently.
38 - * Messages to the IRC server are done by calling methods on an IRC
39 - connection object.
40 - * Messages from an IRC server triggers events, which can be caught
41 - by event handlers.
42 - * Reading from and writing to IRC server sockets are normally done
43 - by an internal select() loop, but the select()ing may be done by
44 - an external main loop.
45 - * Functions can be registered to execute at specified times by the
46 - event-loop.
47 - * Decodes CTCP tagging correctly (hopefully); I haven't seen any
48 - other IRC client implementation that handles the CTCP
49 - specification subtilties.
50 - * A kind of simple, single-server, object-oriented IRC client class
51 - that dispatches events to instance methods is included.
52 -
53 -Current limitations:
54 -
55 - * The IRC protocol shines through the abstraction a bit too much.
56 - * Data is not written asynchronously to the server, i.e. the write()
57 - may block if the TCP buffers are stuffed.
58 - * There are no support for DCC connections.
59 - * The author haven't even read RFC 2810, 2811, 2812 and 2813.
60 - * Like most projects, documentation is lacking...
61 -
62 -Since I seldom use IRC anymore, I will probably not work much on the
63 -library. If you want to help or continue developing the library,
64 -please contact me (Joel Rosdahl <joel@rosdahl.net>).
65 -
66 -.. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
67 -"""
68 -
69 -import bisect
70 -import re
71 -import select
72 -import socket
73 -import string
74 -import sys
75 -import time
76 -import types
77 -
78 -VERSION = 0, 4, 2
79 -DEBUG = 0
80 -
81 -# TODO
82 -# ----
83 -# (maybe) thread safety
84 -# (maybe) color parser convenience functions
85 -# documentation (including all event types)
86 -# (maybe) add awareness of different types of ircds
87 -# send data asynchronously to the server (and DCC connections)
88 -# (maybe) automatically close unused, passive DCC connections after a while
89 -
90 -# NOTES
91 -# -----
92 -# connection.quit() only sends QUIT to the server.
93 -# ERROR from the server triggers the error event and the disconnect event.
94 -# dropping of the connection triggers the disconnect event.
95 -
96 -class IRCError(Exception):
97 - """Represents an IRC exception."""
98 - pass
99 -
100 -
101 -class IRC:
102 - """Class that handles one or several IRC server connections.
103 -
104 - When an IRC object has been instantiated, it can be used to create
105 - Connection objects that represent the IRC connections. The
106 - responsibility of the IRC object is to provide an event-driven
107 - framework for the connections and to keep the connections alive.
108 - It runs a select loop to poll each connection's TCP socket and
109 - hands over the sockets with incoming data for processing by the
110 - corresponding connection.
111 -
112 - The methods of most interest for an IRC client writer are server,
113 - add_global_handler, remove_global_handler, execute_at,
114 - execute_delayed, process_once and process_forever.
115 -
116 - Here is an example:
117 -
118 - irc = irclib.IRC()
119 - server = irc.server()
120 - server.connect(\"irc.some.where\", 6667, \"my_nickname\")
121 - server.privmsg(\"a_nickname\", \"Hi there!\")
122 - server.process_forever()
123 -
124 - This will connect to the IRC server irc.some.where on port 6667
125 - using the nickname my_nickname and send the message \"Hi there!\"
126 - to the nickname a_nickname.
127 - """
128 -
129 - def __init__(self, fn_to_add_socket=None,
130 - fn_to_remove_socket=None,
131 - fn_to_add_timeout=None):
132 - """Constructor for IRC objects.
133 -
134 - Optional arguments are fn_to_add_socket, fn_to_remove_socket
135 - and fn_to_add_timeout. The first two specify functions that
136 - will be called with a socket object as argument when the IRC
137 - object wants to be notified (or stop being notified) of data
138 - coming on a new socket. When new data arrives, the method
139 - process_data should be called. Similarly, fn_to_add_timeout
140 - is called with a number of seconds (a floating point number)
141 - as first argument when the IRC object wants to receive a
142 - notification (by calling the process_timeout method). So, if
143 - e.g. the argument is 42.17, the object wants the
144 - process_timeout method to be called after 42 seconds and 170
145 - milliseconds.
146 -
147 - The three arguments mainly exist to be able to use an external
148 - main loop (for example Tkinter's or PyGTK's main app loop)
149 - instead of calling the process_forever method.
150 -
151 - An alternative is to just call ServerConnection.process_once()
152 - once in a while.
153 - """
154 -
155 - if fn_to_add_socket and fn_to_remove_socket:
156 - self.fn_to_add_socket = fn_to_add_socket
157 - self.fn_to_remove_socket = fn_to_remove_socket
158 - else:
159 - self.fn_to_add_socket = None
160 - self.fn_to_remove_socket = None
161 -
162 - self.fn_to_add_timeout = fn_to_add_timeout
163 - self.connections = []
164 - self.handlers = {}
165 - self.delayed_commands = [] # list of tuples in the format (time, function, arguments)
166 -
167 - self.add_global_handler("ping", _ping_ponger, -42)
168 -
169 - def server(self):
170 - """Creates and returns a ServerConnection object."""
171 -
172 - c = ServerConnection(self)
173 - self.connections.append(c)
174 - return c
175 -
176 - def process_data(self, sockets):
177 - """Called when there is more data to read on connection sockets.
178 -
179 - Arguments:
180 -
181 - sockets -- A list of socket objects.
182 -
183 - See documentation for IRC.__init__.
184 - """
185 - for s in sockets:
186 - for c in self.connections:
187 - if s == c._get_socket():
188 - c.process_data()
189 -
190 - def process_timeout(self):
191 - """Called when a timeout notification is due.
192 -
193 - See documentation for IRC.__init__.
194 - """
195 - t = time.time()
196 - while self.delayed_commands:
197 - if t >= self.delayed_commands[0][0]:
198 - apply(self.delayed_commands[0][1], self.delayed_commands[0][2])
199 - del self.delayed_commands[0]
200 - else:
201 - break
202 -
203 - def process_once(self, timeout=0):
204 - """Process data from connections once.
205 -
206 - Arguments:
207 -
208 - timeout -- How long the select() call should wait if no
209 - data is available.
210 -
211 - This method should be called periodically to check and process
212 - incoming data, if there are any. If that seems boring, look
213 - at the process_forever method.
214 - """
215 - sockets = map(lambda x: x._get_socket(), self.connections)
216 - sockets = filter(lambda x: x != None, sockets)
217 - if sockets:
218 - (i, o, e) = select.select(sockets, [], [], timeout)
219 - self.process_data(i)
220 - else:
221 - time.sleep(timeout)
222 - self.process_timeout()
223 -
224 - def process_forever(self, timeout=0.2):
225 - """Run an infinite loop, processing data from connections.
226 -
227 - This method repeatedly calls process_once.
228 -
229 - Arguments:
230 -
231 - timeout -- Parameter to pass to process_once.
232 - """
233 - while 1:
234 - self.process_once(timeout)
235 -
236 - def disconnect_all(self, message=""):
237 - """Disconnects all connections."""
238 - for c in self.connections:
239 - c.quit(message)
240 - c.disconnect(message)
241 -
242 - def add_global_handler(self, event, handler, priority=0):
243 - """Adds a global handler function for a specific event type.
244 -
245 - Arguments:
246 -
247 - event -- Event type (a string). Check the values of the
248 - numeric_events dictionary in irclib.py for possible event
249 - types.
250 -
251 - handler -- Callback function.
252 -
253 - priority -- A number (the lower number, the higher priority).
254 -
255 - The handler function is called whenever the specified event is
256 - triggered in any of the connections. See documentation for
257 - the Event class.
258 -
259 - The handler functions are called in priority order (lowest
260 - number is highest priority). If a handler function returns
261 - \"NO MORE\", no more handlers will be called.
262 - """
263 -
264 - if not self.handlers.has_key(event):
265 - self.handlers[event] = []
266 - bisect.insort(self.handlers[event], ((priority, handler)))
267 -
268 - def remove_global_handler(self, event, handler):
269 - """Removes a global handler function.
270 -
271 - Arguments:
272 -
273 - event -- Event type (a string).
274 -
275 - handler -- Callback function.
276 -
277 - Returns 1 on success, otherwise 0.
278 - """
279 - if not self.handlers.has_key(event):
280 - return 0
281 - for h in self.handlers[event]:
282 - if handler == h[1]:
283 - self.handlers[event].remove(h)
284 - return 1
285 -
286 - def execute_at(self, at, function, arguments=()):
287 - """Execute a function at a specified time.
288 -
289 - Arguments:
290 -
291 - at -- Execute at this time (standard \"time_t\" time).
292 -
293 - function -- Function to call.
294 -
295 - arguments -- Arguments to give the function.
296 - """
297 - self.execute_delayed(at-time.time(), function, arguments)
298 -
299 - def execute_delayed(self, delay, function, arguments=()):
300 - """Execute a function after a specified time.
301 -
302 - Arguments:
303 -
304 - delay -- How many seconds to wait.
305 -
306 - function -- Function to call.
307 -
308 - arguments -- Arguments to give the function.
309 - """
310 - bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments))
311 - if self.fn_to_add_timeout:
312 - self.fn_to_add_timeout(delay)
313 -
314 - def dcc(self, dcctype="chat"):
315 - """Creates and returns a DCCConnection object.
316 -
317 - Arguments:
318 -
319 - dcctype -- "chat" for DCC CHAT connections or "raw" for
320 - DCC SEND (or other DCC types). If "chat",
321 - incoming data will be split in newline-separated
322 - chunks. If "raw", incoming data is not touched.
323 - """
324 - c = DCCConnection(self, dcctype)
325 - self.connections.append(c)
326 - return c
327 -
328 - def _handle_event(self, connection, event):
329 - """[Internal]"""
330 - h = self.handlers
331 - for handler in h.get("all_events", []) + h.get(event.eventtype(), []):
332 - if handler[1](connection, event) == "NO MORE":
333 - return
334 -
335 - def _remove_connection(self, connection):
336 - """[Internal]"""
337 - self.connections.remove(connection)
338 - if self.fn_to_remove_socket:
339 - self.fn_to_remove_socket(connection._get_socket())
340 -
341 -_rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
342 -
343 -
344 -class Connection:
345 - """Base class for IRC connections.
346 -
347 - Must be overridden.
348 - """
349 - def __init__(self, irclibobj):
350 - self.irclibobj = irclibobj
351 -
352 - def _get_socket():
353 - raise IRCError, "Not overridden"
354 -
355 - ##############################
356 - ### Convenience wrappers.
357 -
358 - def execute_at(self, at, function, arguments=()):
359 - self.irclibobj.execute_at(at, function, arguments)
360 -
361 - def execute_delayed(self, delay, function, arguments=()):
362 - self.irclibobj.execute_delayed(delay, function, arguments)
363 -
364 -
365 -class ServerConnectionError(IRCError):
366 - pass
367 -
368 -class ServerNotConnectedError(ServerConnectionError):
369 - pass
370 -
371 -
372 -# Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
373 -# use \n as message separator! :P
374 -_linesep_regexp = re.compile("\r?\n")
375 -
376 -class ServerConnection(Connection):
377 - """This class represents an IRC server connection.
378 -
379 - ServerConnection objects are instantiated by calling the server
380 - method on an IRC object.
381 - """
382 -
383 - def __init__(self, irclibobj):
384 - Connection.__init__(self, irclibobj)
385 - self.connected = 0 # Not connected yet.
386 - self.connect_time = 0
387 -
388 - def connect(self, server, port, nickname, password=None, username=None,
389 - ircname=None, localaddress="", localport=0):
390 - """Connect/reconnect to a server.
391 -
392 - Arguments:
393 -
394 - server -- Server name.
395 -
396 - port -- Port number.
397 -
398 - nickname -- The nickname.
399 -
400 - password -- Password (if any).
401 -
402 - username -- The username.
403 -
404 - ircname -- The IRC name ("realname").
405 -
406 - localaddress -- Bind the connection to a specific local IP address.
407 -
408 - localport -- Bind the connection to a specific local port.
409 -
410 - This function can be called to reconnect a closed connection.
411 -
412 - Returns the ServerConnection object.
413 - """
414 -
415 - # flood control
416 - delay = self.connect_time - time.time() + 60
417 - if self.connect_time and delay > 0:
418 - time.sleep(delay)
419 -
420 - self.connect_time = time.time()
421 -
422 - if self.connected:
423 - self.quit("Changing server")
424 -
425 - self.socket = None
426 - self.previous_buffer = ""
427 - self.handlers = {}
428 - self.real_server_name = ""
429 - self.real_nickname = nickname
430 - self.server = server
431 - self.port = port
432 - self.nickname = nickname
433 - self.username = username or nickname
434 - self.ircname = ircname or nickname
435 - self.password = password
436 - self.localaddress = localaddress
437 - self.localport = localport
438 - self.localhost = socket.gethostname()
439 - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
440 - try:
441 - self.socket.bind((self.localaddress, self.localport))
442 - self.socket.connect((self.server, self.port))
443 - except socket.error, x:
444 - raise ServerConnectionError, "Couldn't connect to socket: %s" % x
445 - self.connected = 1
446 - if self.irclibobj.fn_to_add_socket:
447 - self.irclibobj.fn_to_add_socket(self.socket)
448 -
449 - # Log on...
450 - if self.password:
451 - self.pass_(self.password)
452 - self.nick(self.nickname)
453 - self.user(self.username, self.ircname)
454 - return self
455 -
456 - def close(self):
457 - """Close the connection.
458 -
459 - This method closes the connection permanently; after it has
460 - been called, the object is unusable.
461 - """
462 -
463 - self.disconnect("Closing object")
464 - self.irclibobj._remove_connection(self)
465 -
466 - def _get_socket(self):
467 - """[Internal]"""
468 - return self.socket
469 -
470 - def get_server_name(self):
471 - """Get the (real) server name.
472 -
473 - This method returns the (real) server name, or, more
474 - specifically, what the server calls itself.
475 - """
476 -
477 - if self.real_server_name:
478 - return self.real_server_name
479 - else:
480 - return ""
481 -
482 - def get_nickname(self):
483 - """Get the (real) nick name.
484 -
485 - This method returns the (real) nickname. The library keeps
486 - track of nick changes, so it might not be the nick name that
487 - was passed to the connect() method. """
488 -
489 - return self.real_nickname
490 -
491 - def process_data(self):
492 - """[Internal]"""
493 -
494 - try:
495 - new_data = self.socket.recv(2**14)
496 - except socket.error, x:
497 - # The server hung up.
498 - self.disconnect("Connection reset by peer")
499 - return
500 - if not new_data:
501 - # Read nothing: connection must be down.
502 - self.disconnect("Connection reset by peer")
503 - return
504 -
505 - lines = _linesep_regexp.split(self.previous_buffer + new_data)
506 -
507 - # Save the last, unfinished line.
508 - self.previous_buffer = lines[-1]
509 - lines = lines[:-1]
510 -
511 - for line in lines:
512 - if DEBUG:
513 - print "FROM SERVER:", line
514 -
515 - if not line:
516 - continue
517 -
518 - prefix = None
519 - command = None
520 - arguments = None
521 - self._handle_event(Event("all_raw_messages",
522 - self.get_server_name(),
523 - None,
524 - [line]))
525 -
526 - m = _rfc_1459_command_regexp.match(line)
527 - if m.group("prefix"):
528 - prefix = m.group("prefix")
529 - if not self.real_server_name:
530 - self.real_server_name = prefix
531 -
532 - if m.group("command"):
533 - command = string.lower(m.group("command"))
534 -
535 - if m.group("argument"):
536 - a = string.split(m.group("argument"), " :", 1)
537 - arguments = string.split(a[0])
538 - if len(a) == 2:
539 - arguments.append(a[1])
540 -
541 - if command == "nick":
542 - if nm_to_n(prefix) == self.real_nickname:
543 - self.real_nickname = arguments[0]
544 - elif command == "001":
545 - # Record the nickname in case the client changed nick
546 - # in a nicknameinuse callback.
547 - self.real_nickname = arguments[0]
548 -
549 - if command in ["privmsg", "notice"]:
550 - target, message = arguments[0], arguments[1]
551 - messages = _ctcp_dequote(message)
552 -
553 - if command == "privmsg":
554 - if is_channel(target):
555 - command = "pubmsg"
556 - else:
557 - if is_channel(target):
558 - command = "pubnotice"
559 - else:
560 - command = "privnotice"
561 -
562 - for m in messages:
563 - if type(m) is types.TupleType:
564 - if command in ["privmsg", "pubmsg"]:
565 - command = "ctcp"
566 - else:
567 - command = "ctcpreply"
568 -
569 - m = list(m)
570 - if DEBUG:
571 - print "command: %s, source: %s, target: %s, arguments: %s" % (
572 - command, prefix, target, m)
573 - self._handle_event(Event(command, prefix, target, m))
574 - else:
575 - if DEBUG:
576 - print "command: %s, source: %s, target: %s, arguments: %s" % (
577 - command, prefix, target, [m])
578 - self._handle_event(Event(command, prefix, target, [m]))
579 - else:
580 - target = None
581 -
582 - if command == "quit":
583 - arguments = [arguments[0]]
584 - elif command == "ping":
585 - target = arguments[0]
586 - else:
587 - target = arguments[0]
588 - arguments = arguments[1:]
589 -
590 - if command == "mode":
591 - if not is_channel(target):
592 - command = "umode"
593 -
594 - # Translate numerics into more readable strings.
595 - if numeric_events.has_key(command):
596 - command = numeric_events[command]
597 -
598 - if DEBUG:
599 - print "command: %s, source: %s, target: %s, arguments: %s" % (
600 - command, prefix, target, arguments)
601 - self._handle_event(Event(command, prefix, target, arguments))
602 -
603 - def _handle_event(self, event):
604 - """[Internal]"""
605 - self.irclibobj._handle_event(self, event)
606 - if self.handlers.has_key(event.eventtype()):
607 - for fn in self.handlers[event.eventtype()]:
608 - fn(self, event)
609 -
610 - def is_connected(self):
611 - """Return connection status.
612 -
613 - Returns true if connected, otherwise false.
614 - """
615 - return self.connected
616 -
617 - def add_global_handler(self, *args):
618 - """Add global handler.
619 -
620 - See documentation for IRC.add_global_handler.
621 - """
622 - apply(self.irclibobj.add_global_handler, args)
623 -
624 - def remove_global_handler(self, *args):
625 - """Remove global handler.
626 -
627 - See documentation for IRC.remove_global_handler.
628 - """
629 - apply(self.irclibobj.remove_global_handler, args)
630 -
631 - def action(self, target, action):
632 - """Send a CTCP ACTION command."""
633 - self.ctcp("ACTION", target, action)
634 -
635 - def admin(self, server=""):
636 - """Send an ADMIN command."""
637 - self.send_raw(string.strip(string.join(["ADMIN", server])))
638 -
639 - def ctcp(self, ctcptype, target, parameter=""):
640 - """Send a CTCP command."""
641 - ctcptype = string.upper(ctcptype)
642 - self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or ""))
643 -
644 - def ctcp_reply(self, target, parameter):
645 - """Send a CTCP REPLY command."""
646 - self.notice(target, "\001%s\001" % parameter)
647 -
648 - def disconnect(self, message=""):
649 - """Hang up the connection.
650 -
651 - Arguments:
652 -
653 - message -- Quit message.
654 - """
655 - if not self.connected:
656 - return
657 -
658 - self.connected = 0
659 - try:
660 - self.socket.close()
661 - except socket.error, x:
662 - pass
663 - self.socket = None
664 - self._handle_event(Event("disconnect", self.server, "", [message]))
665 -
666 - def globops(self, text):
667 - """Send a GLOBOPS command."""
668 - self.send_raw("GLOBOPS :" + text)
669 -
670 - def info(self, server=""):
671 - """Send an INFO command."""
672 - self.send_raw(string.strip(string.join(["INFO", server])))
673 -
674 - def invite(self, nick, channel):
675 - """Send an INVITE command."""
676 - self.send_raw(string.strip(string.join(["INVITE", nick, channel])))
677 -
678 - def ison(self, nicks):
679 - """Send an ISON command.
680 -
681 - Arguments:
682 -
683 - nicks -- List of nicks.
684 - """
685 - self.send_raw("ISON " + string.join(nicks, " "))
686 -
687 - def join(self, channel, key=""):
688 - """Send a JOIN command."""
689 - self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
690 -
691 - def kick(self, channel, nick, comment=""):
692 - """Send a KICK command."""
693 - self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment))))
694 -
695 - def links(self, remote_server="", server_mask=""):
696 - """Send a LINKS command."""
697 - command = "LINKS"
698 - if remote_server:
699 - command = command + " " + remote_server
700 - if server_mask:
701 - command = command + " " + server_mask
702 - self.send_raw(command)
703 -
704 - def list(self, channels=None, server=""):
705 - """Send a LIST command."""
706 - command = "LIST"
707 - if channels:
708 - command = command + " " + string.join(channels, ",")
709 - if server:
710 - command = command + " " + server
711 - self.send_raw(command)
712 -
713 - def lusers(self, server=""):
714 - """Send a LUSERS command."""
715 - self.send_raw("LUSERS" + (server and (" " + server)))
716 -
717 - def mode(self, target, command):
718 - """Send a MODE command."""
719 - self.send_raw("MODE %s %s" % (target, command))
720 -
721 - def motd(self, server=""):
722 - """Send an MOTD command."""
723 - self.send_raw("MOTD" + (server and (" " + server)))
724 -
725 - def names(self, channels=None):
726 - """Send a NAMES command."""
727 - self.send_raw("NAMES" + (channels and (" " + string.join(channels, ",")) or ""))
728 -
729 - def nick(self, newnick):
730 - """Send a NICK command."""
731 - self.send_raw("NICK " + newnick)
732 -
733 - def notice(self, target, text):
734 - """Send a NOTICE command."""
735 - # Should limit len(text) here!
736 - self.send_raw("NOTICE %s :%s" % (target, text))
737 -
738 - def oper(self, nick, password):
739 - """Send an OPER command."""
740 - self.send_raw("OPER %s %s" % (nick, password))
741 -
742 - def part(self, channels):
743 - """Send a PART command."""
744 - if type(channels) == types.StringType:
745 - self.send_raw("PART " + channels)
746 - else:
747 - self.send_raw("PART " + string.join(channels, ","))
748 -
749 - def pass_(self, password):
750 - """Send a PASS command."""
751 - self.send_raw("PASS " + password)
752 -
753 - def ping(self, target, target2=""):
754 - """Send a PING command."""
755 - self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
756 -
757 - def pong(self, target, target2=""):
758 - """Send a PONG command."""
759 - self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
760 -
761 - def privmsg(self, target, text):
762 - """Send a PRIVMSG command."""
763 - # Should limit len(text) here!
764 - self.send_raw("PRIVMSG %s :%s" % (target, text))
765 -
766 - def privmsg_many(self, targets, text):
767 - """Send a PRIVMSG command to multiple targets."""
768 - # Should limit len(text) here!
769 - self.send_raw("PRIVMSG %s :%s" % (string.join(targets, ","), text))
770 -
771 - def quit(self, message=""):
772 - """Send a QUIT command."""
773 - self.send_raw("QUIT" + (message and (" :" + message)))
774 -
775 - def sconnect(self, target, port="", server=""):
776 - """Send an SCONNECT command."""
777 - self.send_raw("CONNECT %s%s%s" % (target,
778 - port and (" " + port),
779 - server and (" " + server)))
780 -
781 - def send_raw(self, string):
782 - """Send raw string to the server.
783 -
784 - The string will be padded with appropriate CR LF.
785 - """
786 - if self.socket is None:
787 - raise ServerNotConnectedError, "Not connected."
788 - try:
789 - self.socket.send(string + "\r\n")
790 - if DEBUG:
791 - print "TO SERVER:", string
792 - except socket.error, x:
793 - # Ouch!
794 - self.disconnect("Connection reset by peer.")
795 -
796 - def squit(self, server, comment=""):
797 - """Send an SQUIT command."""
798 - self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
799 -
800 - def stats(self, statstype, server=""):
801 - """Send a STATS command."""
802 - self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
803 -
804 - def time(self, server=""):
805 - """Send a TIME command."""
806 - self.send_raw("TIME" + (server and (" " + server)))
807 -
808 - def topic(self, channel, new_topic=None):
809 - """Send a TOPIC command."""
810 - if new_topic == None:
811 - self.send_raw("TOPIC " + channel)
812 - else:
813 - self.send_raw("TOPIC %s :%s" % (channel, new_topic))
814 -
815 - def trace(self, target=""):
816 - """Send a TRACE command."""
817 - self.send_raw("TRACE" + (target and (" " + target)))
818 -
819 - def user(self, username, realname):
820 - """Send a USER command."""
821 - self.send_raw("USER %s 0 * :%s" % (username, realname))
822 -
823 - def userhost(self, nicks):
824 - """Send a USERHOST command."""
825 - self.send_raw("USERHOST " + string.join(nicks, ","))
826 -
827 - def users(self, server=""):
828 - """Send a USERS command."""
829 - self.send_raw("USERS" + (server and (" " + server)))
830 -
831 - def version(self, server=""):
832 - """Send a VERSION command."""
833 - self.send_raw("VERSION" + (server and (" " + server)))
834 -
835 - def wallops(self, text):
836 - """Send a WALLOPS command."""
837 - self.send_raw("WALLOPS :" + text)
838 -
839 - def who(self, target="", op=""):
840 - """Send a WHO command."""
841 - self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
842 -
843 - def whois(self, targets):
844 - """Send a WHOIS command."""
845 - self.send_raw("WHOIS " + string.join(targets, ","))
846 -
847 - def whowas(self, nick, max="", server=""):
848 - """Send a WHOWAS command."""
849 - self.send_raw("WHOWAS %s%s%s" % (nick,
850 - max and (" " + max),
851 - server and (" " + server)))
852 -
853 -
854 -class DCCConnectionError(IRCError):
855 - pass
856 -
857 -
858 -class DCCConnection(Connection):
859 - """This class represents a DCC connection.
860 -
861 - DCCConnection objects are instantiated by calling the dcc
862 - method on an IRC object.
863 - """
864 - def __init__(self, irclibobj, dcctype):
865 - Connection.__init__(self, irclibobj)
866 - self.connected = 0
867 - self.passive = 0
868 - self.dcctype = dcctype
869 - self.peeraddress = None
870 - self.peerport = None
871 -
872 - def connect(self, address, port):
873 - """Connect/reconnect to a DCC peer.
874 -
875 - Arguments:
876 - address -- Host/IP address of the peer.
877 -
878 - port -- The port number to connect to.
879 -
880 - Returns the DCCConnection object.
881 - """
882 - self.peeraddress = socket.gethostbyname(address)
883 - self.peerport = port
884 - self.socket = None
885 - self.previous_buffer = ""
886 - self.handlers = {}
887 - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
888 - self.passive = 0
889 - try:
890 - self.socket.connect((self.peeraddress, self.peerport))
891 - except socket.error, x:
892 - raise DCCConnectionError, "Couldn't connect to socket: %s" % x
893 - self.connected = 1
894 - if self.irclibobj.fn_to_add_socket:
895 - self.irclibobj.fn_to_add_socket(self.socket)
896 - return self
897 -
898 - def listen(self):
899 - """Wait for a connection/reconnection from a DCC peer.
900 -
901 - Returns the DCCConnection object.
902 -
903 - The local IP address and port are available as
904 - self.localaddress and self.localport. After connection from a
905 - peer, the peer address and port are available as
906 - self.peeraddress and self.peerport.
907 - """
908 - self.previous_buffer = ""
909 - self.handlers = {}
910 - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
911 - self.passive = 1
912 - try:
913 - self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
914 - self.localaddress, self.localport = self.socket.getsockname()
915 - self.socket.listen(10)
916 - except socket.error, x:
917 - raise DCCConnectionError, "Couldn't bind socket: %s" % x
918 - return self
919 -
920 - def disconnect(self, message=""):
921 - """Hang up the connection and close the object.
922 -
923 - Arguments:
924 -
925 - message -- Quit message.
926 - """
927 - if not self.connected:
928 - return
929 -
930 - self.connected = 0
931 - try:
932 - self.socket.close()
933 - except socket.error, x:
934 - pass
935 - self.socket = None
936 - self.irclibobj._handle_event(
937 - self,
938 - Event("dcc_disconnect", self.peeraddress, "", [message]))
939 - self.irclibobj._remove_connection(self)
940 -
941 - def process_data(self):
942 - """[Internal]"""
943 -
944 - if self.passive and not self.connected:
945 - conn, (self.peeraddress, self.peerport) = self.socket.accept()
946 - self.socket.close()
947 - self.socket = conn
948 - self.connected = 1
949 - if DEBUG:
950 - print "DCC connection from %s:%d" % (
951 - self.peeraddress, self.peerport)
952 - self.irclibobj._handle_event(
953 - self,
954 - Event("dcc_connect", self.peeraddress, None, None))
955 - return
956 -
957 - try:
958 - new_data = self.socket.recv(2**14)
959 - except socket.error, x:
960 - # The server hung up.
961 - self.disconnect("Connection reset by peer")
962 - return
963 - if not new_data:
964 - # Read nothing: connection must be down.
965 - self.disconnect("Connection reset by peer")
966 - return
967 -
968 - if self.dcctype == "chat":
969 - # The specification says lines are terminated with LF, but
970 - # it seems safer to handle CR LF terminations too.
971 - chunks = _linesep_regexp.split(self.previous_buffer + new_data)
972 -
973 - # Save the last, unfinished line.
974 - self.previous_buffer = chunks[-1]
975 - if len(self.previous_buffer) > 2**14:
976 - # Bad peer! Naughty peer!
977 - self.disconnect()
978 - return
979 - chunks = chunks[:-1]
980 - else:
981 - chunks = [new_data]
982 -
983 - command = "dccmsg"
984 - prefix = self.peeraddress
985 - target = None
986 - for chunk in chunks:
987 - if DEBUG:
988 - print "FROM PEER:", chunk
989 - arguments = [chunk]
990 - if DEBUG:
991 - print "command: %s, source: %s, target: %s, arguments: %s" % (
992 - command, prefix, target, arguments)
993 - self.irclibobj._handle_event(
994 - self,
995 - Event(command, prefix, target, arguments))
996 -
997 - def _get_socket(self):
998 - """[Internal]"""
999 - return self.socket
1000 -
1001 - def privmsg(self, string):
1002 - """Send data to DCC peer.
1003 -
1004 - The string will be padded with appropriate LF if it's a DCC
1005 - CHAT session.
1006 - """
1007 - try:
1008 - self.socket.send(string)
1009 - if self.dcctype == "chat":
1010 - self.socket.send("\n")
1011 - if DEBUG:
1012 - print "TO PEER: %s\n" % string
1013 - except socket.error, x:
1014 - # Ouch!
1015 - self.disconnect("Connection reset by peer.")
1016 -
1017 -class SimpleIRCClient:
1018 - """A simple single-server IRC client class.
1019 -
1020 - This is an example of an object-oriented wrapper of the IRC
1021 - framework. A real IRC client can be made by subclassing this
1022 - class and adding appropriate methods.
1023 -
1024 - The method on_join will be called when a "join" event is created
1025 - (which is done when the server sends a JOIN messsage/command),
1026 - on_privmsg will be called for "privmsg" events, and so on. The
1027 - handler methods get two arguments: the connection object (same as
1028 - self.connection) and the event object.
1029 -
1030 - Instance attributes that can be used by sub classes:
1031 -
1032 - ircobj -- The IRC instance.
1033 -
1034 - connection -- The ServerConnection instance.
1035 -
1036 - dcc_connections -- A list of DCCConnection instances.
1037 - """
1038 - def __init__(self):
1039 - self.ircobj = IRC()
1040 - self.connection = self.ircobj.server()
1041 - self.dcc_connections = []
1042 - self.ircobj.add_global_handler("all_events", self._dispatcher, -10)
1043 - self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10)
1044 -
1045 - def _dispatcher(self, c, e):
1046 - """[Internal]"""
1047 - m = "on_" + e.eventtype()
1048 - if hasattr(self, m):
1049 - getattr(self, m)(c, e)
1050 -
1051 - def _dcc_disconnect(self, c, e):
1052 - self.dcc_connections.remove(c)
1053 -
1054 - def connect(self, server, port, nickname, password=None, username=None,
1055 - ircname=None, localaddress="", localport=0):
1056 - """Connect/reconnect to a server.
1057 -
1058 - Arguments:
1059 -
1060 - server -- Server name.
1061 -
1062 - port -- Port number.
1063 -
1064 - nickname -- The nickname.
1065 -
1066 - password -- Password (if any).
1067 -
1068 - username -- The username.
1069 -
1070 - ircname -- The IRC name.
1071 -
1072 - localaddress -- Bind the connection to a specific local IP address.
1073 -
1074 - localport -- Bind the connection to a specific local port.
1075 -
1076 - This function can be called to reconnect a closed connection.
1077 - """
1078 - self.connection.connect(server, port, nickname,
1079 - password, username, ircname,
1080 - localaddress, localport)
1081 -
1082 - def dcc_connect(self, address, port, dcctype="chat"):
1083 - """Connect to a DCC peer.
1084 -
1085 - Arguments:
1086 -
1087 - address -- IP address of the peer.
1088 -
1089 - port -- Port to connect to.
1090 -
1091 - Returns a DCCConnection instance.
1092 - """
1093 - dcc = self.ircobj.dcc(dcctype)
1094 - self.dcc_connections.append(dcc)
1095 - dcc.connect(address, port)
1096 - return dcc
1097 -
1098 - def dcc_listen(self, dcctype="chat"):
1099 - """Listen for connections from a DCC peer.
1100 -
1101 - Returns a DCCConnection instance.
1102 - """
1103 - dcc = self.ircobj.dcc(dcctype)
1104 - self.dcc_connections.append(dcc)
1105 - dcc.listen()
1106 - return dcc
1107 -
1108 - def start(self):
1109 - """Start the IRC client."""
1110 - self.ircobj.process_forever()
1111 -
1112 -
1113 -class Event:
1114 - """Class representing an IRC event."""
1115 - def __init__(self, eventtype, source, target, arguments=None):
1116 - """Constructor of Event objects.
1117 -
1118 - Arguments:
1119 -
1120 - eventtype -- A string describing the event.
1121 -
1122 - source -- The originator of the event (a nick mask or a server). XXX Correct?
1123 -
1124 - target -- The target of the event (a nick or a channel). XXX Correct?
1125 -
1126 - arguments -- Any event specific arguments.
1127 - """
1128 - self._eventtype = eventtype
1129 - self._source = source
1130 - self._target = target
1131 - if arguments:
1132 - self._arguments = arguments
1133 - else:
1134 - self._arguments = []
1135 -
1136 - def eventtype(self):
1137 - """Get the event type."""
1138 - return self._eventtype
1139 -
1140 - def source(self):
1141 - """Get the event source."""
1142 - return self._source
1143 -
1144 - def target(self):
1145 - """Get the event target."""
1146 - return self._target
1147 -
1148 - def arguments(self):
1149 - """Get the event arguments."""
1150 - return self._arguments
1151 -
1152 -_LOW_LEVEL_QUOTE = "\020"
1153 -_CTCP_LEVEL_QUOTE = "\134"
1154 -_CTCP_DELIMITER = "\001"
1155 -
1156 -_low_level_mapping = {
1157 - "0": "\000",
1158 - "n": "\n",
1159 - "r": "\r",
1160 - _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE
1161 -}
1162 -
1163 -_low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)")
1164 -
1165 -def mask_matches(nick, mask):
1166 - """Check if a nick matches a mask.
1167 -
1168 - Returns true if the nick matches, otherwise false.
1169 - """
1170 - nick = irc_lower(nick)
1171 - mask = irc_lower(mask)
1172 - mask = string.replace(mask, "\\", "\\\\")
1173 - for ch in ".$|[](){}+":
1174 - mask = string.replace(mask, ch, "\\" + ch)
1175 - mask = string.replace(mask, "?", ".")
1176 - mask = string.replace(mask, "*", ".*")
1177 - r = re.compile(mask, re.IGNORECASE)
1178 - return r.match(nick)
1179 -
1180 -_alpha = "abcdefghijklmnopqrstuvwxyz"
1181 -_special = "-[]\\`^{}"
1182 -nick_characters = _alpha + string.upper(_alpha) + string.digits + _special
1183 -_ircstring_translation = string.maketrans(string.upper(_alpha) + "[]\\^",
1184 - _alpha + "{}|~")
1185 -
1186 -def irc_lower(s):
1187 - """Returns a lowercased string.
1188 -
1189 - The definition of lowercased comes from the IRC specification (RFC
1190 - 1459).
1191 - """
1192 - return string.translate(s, _ircstring_translation)
1193 -
1194 -def _ctcp_dequote(message):
1195 - """[Internal] Dequote a message according to CTCP specifications.
1196 -
1197 - The function returns a list where each element can be either a
1198 - string (normal message) or a tuple of one or two strings (tagged
1199 - messages). If a tuple has only one element (ie is a singleton),
1200 - that element is the tag; otherwise the tuple has two elements: the
1201 - tag and the data.
1202 -
1203 - Arguments:
1204 -
1205 - message -- The message to be decoded.
1206 - """
1207 -
1208 - def _low_level_replace(match_obj):
1209 - ch = match_obj.group(1)
1210 -
1211 - # If low_level_mapping doesn't have the character as key, we
1212 - # should just return the character.
1213 - return _low_level_mapping.get(ch, ch)
1214 -
1215 - if _LOW_LEVEL_QUOTE in message:
1216 - # Yup, there was a quote. Release the dequoter, man!
1217 - message = _low_level_regexp.sub(_low_level_replace, message)
1218 -
1219 - if _CTCP_DELIMITER not in message:
1220 - return [message]
1221 - else:
1222 - # Split it into parts. (Does any IRC client actually *use*
1223 - # CTCP stacking like this?)
1224 - chunks = string.split(message, _CTCP_DELIMITER)
1225 -
1226 - messages = []
1227 - i = 0
1228 - while i < len(chunks)-1:
1229 - # Add message if it's non-empty.
1230 - if len(chunks[i]) > 0:
1231 - messages.append(chunks[i])
1232 -
1233 - if i < len(chunks)-2:
1234 - # Aye! CTCP tagged data ahead!
1235 - messages.append(tuple(string.split(chunks[i+1], " ", 1)))
1236 -
1237 - i = i + 2
1238 -
1239 - if len(chunks) % 2 == 0:
1240 - # Hey, a lonely _CTCP_DELIMITER at the end! This means
1241 - # that the last chunk, including the delimiter, is a
1242 - # normal message! (This is according to the CTCP
1243 - # specification.)
1244 - messages.append(_CTCP_DELIMITER + chunks[-1])
1245 -
1246 - return messages
1247 -
1248 -def is_channel(string):
1249 - """Check if a string is a channel name.
1250 -
1251 - Returns true if the argument is a channel name, otherwise false.
1252 - """
1253 - return string and string[0] in "#&+!"
1254 -
1255 -def ip_numstr_to_quad(num):
1256 - """Convert an IP number as an integer given in ASCII
1257 - representation (e.g. '3232235521') to an IP address string
1258 - (e.g. '192.168.0.1')."""
1259 - n = long(num)
1260 - p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
1261 - n >> 8 & 0xFF, n & 0xFF]))
1262 - return string.join(p, ".")
1263 -
1264 -def ip_quad_to_numstr(quad):
1265 - """Convert an IP address string (e.g. '192.168.0.1') to an IP
1266 - number as an integer given in ASCII representation
1267 - (e.g. '3232235521')."""
1268 - p = map(long, string.split(quad, "."))
1269 - s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
1270 - if s[-1] == "L":
1271 - s = s[:-1]
1272 - return s
1273 -
1274 -def nm_to_n(s):
1275 - """Get the nick part of a nickmask.
1276 -
1277 - (The source of an Event is a nickmask.)
1278 - """
1279 - return string.split(s, "!")[0]
1280 -
1281 -def nm_to_uh(s):
1282 - """Get the userhost part of a nickmask.
1283 -
1284 - (The source of an Event is a nickmask.)
1285 - """
1286 - return string.split(s, "!")[1]
1287 -
1288 -def nm_to_h(s):
1289 - """Get the host part of a nickmask.
1290 -
1291 - (The source of an Event is a nickmask.)
1292 - """
1293 - return string.split(s, "@")[1]
1294 -
1295 -def nm_to_u(s):
1296 - """Get the user part of a nickmask.
1297 -
1298 - (The source of an Event is a nickmask.)
1299 - """
1300 - s = string.split(s, "!")[1]
1301 - return string.split(s, "@")[0]
1302 -
1303 -def parse_nick_modes(mode_string):
1304 - """Parse a nick mode string.
1305 -
1306 - The function returns a list of lists with three members: sign,
1307 - mode and argument. The sign is \"+\" or \"-\". The argument is
1308 - always None.
1309 -
1310 - Example:
1311 -
1312 - >>> irclib.parse_nick_modes(\"+ab-c\")
1313 - [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1314 - """
1315 -
1316 - return _parse_modes(mode_string, "")
1317 -
1318 -def parse_channel_modes(mode_string):
1319 - """Parse a channel mode string.
1320 -
1321 - The function returns a list of lists with three members: sign,
1322 - mode and argument. The sign is \"+\" or \"-\". The argument is
1323 - None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
1324 -
1325 - Example:
1326 -
1327 - >>> irclib.parse_channel_modes(\"+ab-c foo\")
1328 - [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1329 - """
1330 -
1331 - return _parse_modes(mode_string, "bklvo")
1332 -
1333 -def _parse_modes(mode_string, unary_modes=""):
1334 - """[Internal]"""
1335 - modes = []
1336 - arg_count = 0
1337 -
1338 - # State variable.
1339 - sign = ""
1340 -
1341 - a = string.split(mode_string)
1342 - if len(a) == 0:
1343 - return []
1344 - else:
1345 - mode_part, args = a[0], a[1:]
1346 -
1347 - if mode_part[0] not in "+-":
1348 - return []
1349 - for ch in mode_part:
1350 - if ch in "+-":
1351 - sign = ch
1352 - elif ch == " ":
1353 - collecting_arguments = 1
1354 - elif ch in unary_modes:
1355 - if len(args) >= arg_count + 1:
1356 - modes.append([sign, ch, args[arg_count]])
1357 - arg_count = arg_count + 1
1358 - else:
1359 - modes.append([sign, ch, None])
1360 - else:
1361 - modes.append([sign, ch, None])
1362 - return modes
1363 -
1364 -def _ping_ponger(connection, event):
1365 - """[Internal]"""
1366 - connection.pong(event.target())
1367 -
1368 -# Numeric table mostly stolen from the Perl IRC module (Net::IRC).
1369 -numeric_events = {
1370 - "001": "welcome",
1371 - "002": "yourhost",
1372 - "003": "created",
1373 - "004": "myinfo",
1374 - "005": "featurelist", # XXX
1375 - "200": "tracelink",
1376 - "201": "traceconnecting",
1377 - "202": "tracehandshake",
1378 - "203": "traceunknown",
1379 - "204": "traceoperator",
1380 - "205": "traceuser",
1381 - "206": "traceserver",
1382 - "207": "traceservice",
1383 - "208": "tracenewtype",
1384 - "209": "traceclass",
1385 - "210": "tracereconnect",
1386 - "211": "statslinkinfo",
1387 - "212": "statscommands",
1388 - "213": "statscline",
1389 - "214": "statsnline",
1390 - "215": "statsiline",
1391 - "216": "statskline",
1392 - "217": "statsqline",
1393 - "218": "statsyline",
1394 - "219": "endofstats",
1395 - "221": "umodeis",
1396 - "231": "serviceinfo",
1397 - "232": "endofservices",
1398 - "233": "service",
1399 - "234": "servlist",
1400 - "235": "servlistend",
1401 - "241": "statslline",
1402 - "242": "statsuptime",
1403 - "243": "statsoline",
1404 - "244": "statshline",
1405 - "250": "luserconns",
1406 - "251": "luserclient",
1407 - "252": "luserop",
1408 - "253": "luserunknown",
1409 - "254": "luserchannels",
1410 - "255": "luserme",
1411 - "256": "adminme",
1412 - "257": "adminloc1",
1413 - "258": "adminloc2",
1414 - "259": "adminemail",
1415 - "261": "tracelog",
1416 - "262": "endoftrace",
1417 - "263": "tryagain",
1418 - "265": "n_local",
1419 - "266": "n_global",
1420 - "300": "none",
1421 - "301": "away",
1422 - "302": "userhost",
1423 - "303": "ison",
1424 - "305": "unaway",
1425 - "306": "nowaway",
1426 - "311": "whoisuser",
1427 - "312": "whoisserver",
1428 - "313": "whoisoperator",
1429 - "314": "whowasuser",
1430 - "315": "endofwho",
1431 - "316": "whoischanop",
1432 - "317": "whoisidle",
1433 - "318": "endofwhois",
1434 - "319": "whoischannels",
1435 - "321": "liststart",
1436 - "322": "list",
1437 - "323": "listend",
1438 - "324": "channelmodeis",
1439 - "329": "channelcreate",
1440 - "331": "notopic",
1441 - "332": "topic",
1442 - "333": "topicinfo",
1443 - "341": "inviting",
1444 - "342": "summoning",
1445 - "346": "invitelist",
1446 - "347": "endofinvitelist",
1447 - "348": "exceptlist",
1448 - "349": "endofexceptlist",
1449 - "351": "version",
1450 - "352": "whoreply",
1451 - "353": "namreply",
1452 - "361": "killdone",
1453 - "362": "closing",
1454 - "363": "closeend",
1455 - "364": "links",
1456 - "365": "endoflinks",
1457 - "366": "endofnames",
1458 - "367": "banlist",
1459 - "368": "endofbanlist",
1460 - "369": "endofwhowas",
1461 - "371": "info",
1462 - "372": "motd",
1463 - "373": "infostart",
1464 - "374": "endofinfo",
1465 - "375": "motdstart",
1466 - "376": "endofmotd",
1467 - "377": "motd2", # 1997-10-16 -- tkil
1468 - "381": "youreoper",
1469 - "382": "rehashing",
1470 - "384": "myportis",
1471 - "391": "time",
1472 - "392": "usersstart",
1473 - "393": "users",
1474 - "394": "endofusers",
1475 - "395": "nousers",
1476 - "401": "nosuchnick",
1477 - "402": "nosuchserver",
1478 - "403": "nosuchchannel",
1479 - "404": "cannotsendtochan",
1480 - "405": "toomanychannels",
1481 - "406": "wasnosuchnick",
1482 - "407": "toomanytargets",
1483 - "409": "noorigin",
1484 - "411": "norecipient",
1485 - "412": "notexttosend",
1486 - "413": "notoplevel",
1487 - "414": "wildtoplevel",
1488 - "421": "unknowncommand",
1489 - "422": "nomotd",
1490 - "423": "noadmininfo",
1491 - "424": "fileerror",
1492 - "431": "nonicknamegiven",
1493 - "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
1494 - "433": "nicknameinuse",
1495 - "436": "nickcollision",
1496 - "437": "unavailresource", # "Nick temporally unavailable"
1497 - "441": "usernotinchannel",
1498 - "442": "notonchannel",
1499 - "443": "useronchannel",
1500 - "444": "nologin",
1501 - "445": "summondisabled",
1502 - "446": "usersdisabled",
1503 - "451": "notregistered",
1504 - "461": "needmoreparams",
1505 - "462": "alreadyregistered",
1506 - "463": "nopermforhost",
1507 - "464": "passwdmismatch",
1508 - "465": "yourebannedcreep", # I love this one...
1509 - "466": "youwillbebanned",
1510 - "467": "keyset",
1511 - "471": "channelisfull",
1512 - "472": "unknownmode",
1513 - "473": "inviteonlychan",
1514 - "474": "bannedfromchan",
1515 - "475": "badchannelkey",
1516 - "476": "badchanmask",
1517 - "477": "nochanmodes", # "Channel doesn't support modes"
1518 - "478": "banlistfull",
1519 - "481": "noprivileges",
1520 - "482": "chanoprivsneeded",
1521 - "483": "cantkillserver",
1522 - "484": "restricted", # Connection is restricted
1523 - "485": "uniqopprivsneeded",
1524 - "491": "nooperhost",
1525 - "492": "noservicehost",
1526 - "501": "umodeunknownflag",
1527 - "502": "usersdontmatch",
1528 -}
1529 -
1530 -generated_events = [
1531 - # Generated events
1532 - "dcc_connect",
1533 - "dcc_disconnect",
1534 - "dccmsg",
1535 - "disconnect",
1536 - "ctcp",
1537 - "ctcpreply",
1538 -]
1539 -
1540 -protocol_events = [
1541 - # IRC protocol events
1542 - "error",
1543 - "join",
1544 - "kick",
1545 - "mode",
1546 - "part",
1547 - "ping",
1548 - "privmsg",
1549 - "privnotice",
1550 - "pubmsg",
1551 - "pubnotice",
1552 - "quit",
1553 -]
1554 -
1555 -all_events = generated_events + protocol_events + numeric_events.values()
Index: trunk/debs/ircecho/debian/control
@@ -6,6 +6,6 @@
77 Standards-Version: 1.0
88
99 Package: ircecho
10 -Depends: python-pyinotify
 10+Depends: python-pyinotify, python-irclib
1111 Architecture: all
1212 Description: IRC bot for echoing from stdin to IRC.
Index: trunk/debs/ircecho/ircecho
@@ -8,15 +8,12 @@
99
1010 import sys
1111 import pyinotify
12 -from optparse import OptionParser
13 -
14 -sys.path.append('/usr/ircecho/lib')
15 -
16 -from ircbot import SingleServerIRCBot
17 -from irclib import nm_to_n
18 -
 12+import ircbot
 13+import irclib
1914 import threading
2015
 16+from optparse import OptionParser
 17+
2118 class EchoReader(threading.Thread):
2219 def __init__(self, bot, chans, infile=''):
2320 threading.Thread.__init__(self)
@@ -51,10 +48,10 @@
5249 except Exception:
5350 pass
5451
55 -class EchoBot(SingleServerIRCBot):
 52+class EchoBot(ircbot.SingleServerIRCBot):
5653 def __init__(self, chans, nickname, server):
5754 print "*** Connecting to IRC server %s..." % server
58 - SingleServerIRCBot.__init__(self, [(server, 6667)], nickname, "IRC echo bot")
 55+ ircbot.SingleServerIRCBot.__init__(self, [(server, 6667)], nickname, "IRC echo bot")
5956 self.chans = chans
6057
6158 def on_nicknameinuse(self, c, e):

Status & tagging log