Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(450)

Side by Side Diff: third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/irc/irclib.py

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

Powered by Google App Engine
This is Rietveld 408576698