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 (at] 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, function, 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, arguments)) 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=False): 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, arguments: %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, arguments: %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 (" " + parameter) 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 (" :" + comment)))) 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 (" " + message))) 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=False): 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, man! 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() 1561