/IRCXBMC/irclib.py
Python | 1556 lines | 1418 code | 32 blank | 106 comment | 39 complexity | e61a15a034009a8736f6934731c7951a MD5 | raw file
Possible License(s): BSD-2-Clause
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.43 2005/12/24 22:12:40 keltus Exp $ 20 21"""irclib -- Internet Relay Chat (IRC) protocol client library. 22 23This library is intended to encapsulate the IRC protocol at a quite 24low level. It provides an event-driven IRC client framework. It has 25a fairly thorough support for the basic IRC protocol, CTCP, DCC chat, 26but DCC file transfers is not yet supported. 27 28In order to understand how to make an IRC client, I'm afraid you more 29or less must understand the IRC specifications. They are available 30here: [IRC specifications]. 31 32The 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 52Current 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 64import bisect 65import re 66import select 67import socket 68import string 69import sys 70import time 71import types 72 73VERSION = 0, 4, 6 74DEBUG = 1 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 91class IRCError(Exception): 92 """Represents an IRC exception.""" 93 pass 94 95 96class 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 258 if not event in self.handlers: 259 self.handlers[event] = [] 260 bisect.insort(self.handlers[event], ((priority, handler))) 261 262 def remove_global_handler(self, event, handler): 263 """Removes a global handler function. 264 265 Arguments: 266 267 event -- Event type (a string). 268 269 handler -- Callback function. 270 271 Returns 1 on success, otherwise 0. 272 """ 273 if not event in self.handlers: 274 return 0 275 for h in self.handlers[event]: 276 if handler == h[1]: 277 self.handlers[event].remove(h) 278 return 1 279 280 def execute_at(self, at, function, arguments=()): 281 """Execute a function at a specified time. 282 283 Arguments: 284 285 at -- Execute at this time (standard \"time_t\" time). 286 287 function -- Function to call. 288 289 arguments -- Arguments to give the function. 290 """ 291 self.execute_delayed(at-time.time(), function, arguments) 292 293 def execute_delayed(self, delay, function, arguments=()): 294 """Execute a function after a specified time. 295 296 Arguments: 297 298 delay -- How many seconds to wait. 299 300 function -- Function to call. 301 302 arguments -- Arguments to give the function. 303 """ 304 bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments)) 305 if self.fn_to_add_timeout: 306 self.fn_to_add_timeout(delay) 307 308 def dcc(self, dcctype="chat"): 309 """Creates and returns a DCCConnection object. 310 311 Arguments: 312 313 dcctype -- "chat" for DCC CHAT connections or "raw" for 314 DCC SEND (or other DCC types). If "chat", 315 incoming data will be split in newline-separated 316 chunks. If "raw", incoming data is not touched. 317 """ 318 c = DCCConnection(self, dcctype) 319 self.connections.append(c) 320 return c 321 322 def _handle_event(self, connection, event): 323 """[Internal]""" 324 h = self.handlers 325 for handler in h.get("all_events", []) + h.get(event.eventtype(), []): 326 if handler[1](connection, event) == "NO MORE": 327 return 328 329 def _remove_connection(self, connection): 330 """[Internal]""" 331 self.connections.remove(connection) 332 if self.fn_to_remove_socket: 333 self.fn_to_remove_socket(connection._get_socket()) 334 335_rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?") 336 337 338class Connection: 339 """Base class for IRC connections. 340 341 Must be overridden. 342 """ 343 def __init__(self, irclibobj): 344 self.irclibobj = irclibobj 345 346 def _get_socket(): 347 raise IRCError, "Not overridden" 348 349 ############################## 350 ### Convenience wrappers. 351 352 def execute_at(self, at, function, arguments=()): 353 self.irclibobj.execute_at(at, function, arguments) 354 355 def execute_delayed(self, delay, function, arguments=()): 356 self.irclibobj.execute_delayed(delay, function, arguments) 357 358 359class ServerConnectionError(IRCError): 360 pass 361 362class ServerNotConnectedError(ServerConnectionError): 363 pass 364 365 366# Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to 367# use \n as message separator! :P 368_linesep_regexp = re.compile("\r?\n") 369 370class ServerConnection(Connection): 371 """This class represents an IRC server connection. 372 373 ServerConnection objects are instantiated by calling the server 374 method on an IRC object. 375 """ 376 377 def __init__(self, irclibobj): 378 Connection.__init__(self, irclibobj) 379 self.connected = 0 # Not connected yet. 380 self.socket = None 381 382 def connect(self, server, port, nickname, password=None, username=None, 383 ircname=None, localaddress="", localport=0): 384 """Connect/reconnect to a server. 385 386 Arguments: 387 388 server -- Server name. 389 390 port -- Port number. 391 392 nickname -- The nickname. 393 394 password -- Password (if any). 395 396 username -- The username. 397 398 ircname -- The IRC name ("realname"). 399 400 localaddress -- Bind the connection to a specific local IP address. 401 402 localport -- Bind the connection to a specific local port. 403 404 This function can be called to reconnect a closed connection. 405 406 Returns the ServerConnection object. 407 """ 408 if self.connected: 409 self.disconnect("Changing servers") 410 411 self.previous_buffer = "" 412 self.handlers = {} 413 self.real_server_name = "" 414 self.real_nickname = nickname 415 self.server = server 416 self.port = port 417 self.nickname = nickname 418 self.username = username or nickname 419 self.ircname = ircname or nickname 420 self.password = password 421 self.localaddress = localaddress 422 self.localport = localport 423 self.localhost = socket.gethostname() 424 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 425 try: 426 self.socket.bind((self.localaddress, self.localport)) 427 self.socket.connect((self.server, self.port)) 428 except socket.error, x: 429 self.socket.close() 430 self.socket = None 431 raise ServerConnectionError, "Couldn't connect to socket: %s" % x 432 self.connected = 1 433 if self.irclibobj.fn_to_add_socket: 434 self.irclibobj.fn_to_add_socket(self.socket) 435 436 # Log on... 437 if self.password: 438 self.pass_(self.password) 439 self.nick(self.nickname) 440 self.user(self.username, self.ircname) 441 return self 442 443 def close(self): 444 """Close the connection. 445 446 This method closes the connection permanently; after it has 447 been called, the object is unusable. 448 """ 449 450 self.disconnect("Closing object") 451 self.irclibobj._remove_connection(self) 452 453 def _get_socket(self): 454 """[Internal]""" 455 return self.socket 456 457 def get_server_name(self): 458 """Get the (real) server name. 459 460 This method returns the (real) server name, or, more 461 specifically, what the server calls itself. 462 """ 463 464 if self.real_server_name: 465 return self.real_server_name 466 else: 467 return "" 468 469 def get_nickname(self): 470 """Get the (real) nick name. 471 472 This method returns the (real) nickname. The library keeps 473 track of nick changes, so it might not be the nick name that 474 was passed to the connect() method. """ 475 476 return self.real_nickname 477 478 def process_data(self): 479 """[Internal]""" 480 481 try: 482 new_data = self.socket.recv(2**14) 483 except socket.error, x: 484 # The server hung up. 485 self.disconnect("Connection reset by peer") 486 return 487 if not new_data: 488 # Read nothing: connection must be down. 489 self.disconnect("Connection reset by peer") 490 return 491 492 lines = _linesep_regexp.split(self.previous_buffer + new_data) 493 494 # Save the last, unfinished line. 495 self.previous_buffer = lines[-1] 496 lines = lines[:-1] 497 498 for line in lines: 499 if DEBUG: 500 print "FROM SERVER:", line 501 502 if not line: 503 continue 504 505 prefix = None 506 command = None 507 arguments = None 508 self._handle_event(Event("all_raw_messages", 509 self.get_server_name(), 510 None, 511 [line])) 512 513 m = _rfc_1459_command_regexp.match(line) 514 if m.group("prefix"): 515 prefix = m.group("prefix") 516 if not self.real_server_name: 517 self.real_server_name = prefix 518 519 if m.group("command"): 520 command = m.group("command").lower() 521 522 if m.group("argument"): 523 a = m.group("argument").split(" :", 1) 524 arguments = a[0].split() 525 if len(a) == 2: 526 arguments.append(a[1]) 527 528 # Translate numerics into more readable strings. 529 if command in numeric_events: 530 command = numeric_events[command] 531 532 if command == "nick": 533 if nm_to_n(prefix) == self.real_nickname: 534 self.real_nickname = arguments[0] 535 elif command == "welcome": 536 # Record the nickname in case the client changed nick 537 # in a nicknameinuse callback. 538 self.real_nickname = arguments[0] 539 if command == "namreply": 540 #DONNO HACK: Auto handle this now coz can't get it working on main page 541 pass 542 if command == "nicknameinuse": 543 #DONNO HACK: Auto handle this now coz it will slow stuff down if handels else wherer 544 self.nickname = self.nickname + "_" 545 self.nick(self.nickname) 546 if command in ["privmsg", "notice"]: 547 target, message = arguments[0], arguments[1] 548 messages = _ctcp_dequote(message) 549 550 if command == "privmsg": 551 if is_channel(target): 552 command = "pubmsg" 553 else: 554 if is_channel(target): 555 command = "pubnotice" 556 else: 557 command = "privnotice" 558 559 for m in messages: 560 if type(m) is types.TupleType: 561 if command in ["privmsg", "pubmsg"]: 562 command = "ctcp" 563 else: 564 command = "ctcpreply" 565 566 m = list(m) 567 if DEBUG: 568 print "command: %s, source: %s, target: %s, arguments: %s" % ( 569 command, prefix, target, m) 570 self._handle_event(Event(command, prefix, target, m)) 571 if command == "ctcp" and m[0] == "ACTION": 572 self._handle_event(Event("action", prefix, target, m[1:])) 573 else: 574 if DEBUG: 575 print "command: %s, source: %s, target: %s, arguments: %s" % ( 576 command, prefix, target, [m]) 577 self._handle_event(Event(command, prefix, target, [m])) 578 else: 579 target = None 580 581 if command == "quit": 582 arguments = [arguments[0]] 583 elif command == "ping": 584 target = arguments[0] 585 else: 586 target = arguments[0] 587 arguments = arguments[1:] 588 589 if command == "mode": 590 if not is_channel(target): 591 command = "umode" 592 593 if DEBUG: 594 print "command: %s, source: %s, target: %s, arguments: %s" % ( 595 command, prefix, target, arguments) 596 self._handle_event(Event(command, prefix, target, arguments)) 597 598 def _handle_event(self, event): 599 """[Internal]""" 600 self.irclibobj._handle_event(self, event) 601 if event.eventtype() in self.handlers: 602 for fn in self.handlers[event.eventtype()]: 603 fn(self, event) 604 605 def is_connected(self): 606 """Return connection status. 607 608 Returns true if connected, otherwise false. 609 """ 610 return self.connected 611 612 def add_global_handler(self, *args): 613 """Add global handler. 614 615 See documentation for IRC.add_global_handler. 616 """ 617 self.irclibobj.add_global_handler(*args) 618 619 def remove_global_handler(self, *args): 620 """Remove global handler. 621 622 See documentation for IRC.remove_global_handler. 623 """ 624 self.irclibobj.remove_global_handler(*args) 625 626 def action(self, target, action): 627 """Send a CTCP ACTION command.""" 628 self.ctcp("ACTION", target, action) 629 630 def admin(self, server=""): 631 """Send an ADMIN command.""" 632 self.send_raw(" ".join(["ADMIN", server]).strip()) 633 634 def ctcp(self, ctcptype, target, parameter=""): 635 """Send a CTCP command.""" 636 ctcptype = ctcptype.upper() 637 self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or "")) 638 639 def ctcp_reply(self, target, parameter): 640 """Send a CTCP REPLY command.""" 641 self.notice(target, "\001%s\001" % parameter) 642 643 def disconnect(self, message=""): 644 """Hang up the connection. 645 646 Arguments: 647 648 message -- Quit message. 649 """ 650 if not self.connected: 651 return 652 653 self.connected = 0 654 655 self.quit(message) 656 657 try: 658 self.socket.close() 659 except socket.error, x: 660 pass 661 self.socket = None 662 self._handle_event(Event("disconnect", self.server, "", [message])) 663 664 def globops(self, text): 665 """Send a GLOBOPS command.""" 666 self.send_raw("GLOBOPS :" + text) 667 668 def info(self, server=""): 669 """Send an INFO command.""" 670 self.send_raw(" ".join(["INFO", server]).strip()) 671 672 def invite(self, nick, channel): 673 """Send an INVITE command.""" 674 self.send_raw(" ".join(["INVITE", nick, channel]).strip()) 675 676 def ison(self, nicks): 677 """Send an ISON command. 678 679 Arguments: 680 681 nicks -- List of nicks. 682 """ 683 self.send_raw("ISON " + " ".join(nicks)) 684 685 def join(self, channel, key=""): 686 """Send a JOIN command.""" 687 self.send_raw("JOIN %s%s" % (channel, (key and (" " + key)))) 688 689 def kick(self, channel, nick, comment=""): 690 """Send a KICK command.""" 691 self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment)))) 692 693 def links(self, remote_server="", server_mask=""): 694 """Send a LINKS command.""" 695 command = "LINKS" 696 if remote_server: 697 command = command + " " + remote_server 698 if server_mask: 699 command = command + " " + server_mask 700 self.send_raw(command) 701 702 def list(self, channels=None, server=""): 703 """Send a LIST command.""" 704 command = "LIST" 705 if channels: 706 command = command + " " + ",".join(channels) 707 if server: 708 command = command + " " + server 709 self.send_raw(command) 710 711 def lusers(self, server=""): 712 """Send a LUSERS command.""" 713 self.send_raw("LUSERS" + (server and (" " + server))) 714 715 def mode(self, target, command): 716 """Send a MODE command.""" 717 self.send_raw("MODE %s %s" % (target, command)) 718 719 def motd(self, server=""): 720 """Send an MOTD command.""" 721 self.send_raw("MOTD" + (server and (" " + server))) 722 723 def names(self, channels=None): 724 """Send a NAMES command.""" 725 self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or "")) 726 727 def nick(self, newnick): 728 """Send a NICK command.""" 729 self.send_raw("NICK " + newnick) 730 731 def notice(self, target, text): 732 """Send a NOTICE command.""" 733 # Should limit len(text) here! 734 self.send_raw("NOTICE %s :%s" % (target, text)) 735 736 def oper(self, nick, password): 737 """Send an OPER command.""" 738 self.send_raw("OPER %s %s" % (nick, password)) 739 740 def part(self, channels, message=""): 741 """Send a PART command.""" 742 if type(channels) == types.StringType: 743 self.send_raw("PART " + channels + (message and (" " + message))) 744 else: 745 self.send_raw("PART " + ",".join(channels) + (message and (" " + message))) 746 747 def pass_(self, password): 748 """Send a PASS command.""" 749 self.send_raw("PASS " + password) 750 751 def ping(self, target, target2=""): 752 """Send a PING command.""" 753 self.send_raw("PING %s%s" % (target, target2 and (" " + target2))) 754 755 def pong(self, target, target2=""): 756 """Send a PONG command.""" 757 self.send_raw("PONG %s%s" % (target, target2 and (" " + target2))) 758 759 def privmsg(self, target, text): 760 """Send a PRIVMSG command.""" 761 # Should limit len(text) here! 762 self.send_raw("PRIVMSG %s :%s" % (target, text)) 763 764 def privmsg_many(self, targets, text): 765 """Send a PRIVMSG command to multiple targets.""" 766 # Should limit len(text) here! 767 self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text)) 768 769 def quit(self, message=""): 770 """Send a QUIT command.""" 771 # Note that many IRC servers don't use your QUIT message 772 # unless you've been connected for at least 5 minutes! 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 is 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 " + ",".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 " + ",".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 854class DCCConnectionError(IRCError): 855 pass 856 857 858class 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 1017class 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 1113class 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). 1123 1124 target -- The target of the event (a nick or a channel). 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 1165def 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 = mask.replace("\\", "\\\\") 1173 for ch in ".$|[](){}+": 1174 mask = mask.replace(ch, "\\" + ch) 1175 mask = mask.replace("?", ".") 1176 mask = mask.replace("*", ".*") 1177 r = re.compile(mask, re.IGNORECASE) 1178 return r.match(nick) 1179 1180_special = "-[]\\`^{}" 1181nick_characters = string.ascii_letters + string.digits + _special 1182_ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^", 1183 string.ascii_lowercase + "{}|~") 1184 1185def irc_lower(s): 1186 """Returns a lowercased string. 1187 1188 The definition of lowercased comes from the IRC specification (RFC 1189 1459). 1190 """ 1191 return s.translate(_ircstring_translation) 1192 1193def _ctcp_dequote(message): 1194 """[Internal] Dequote a message according to CTCP specifications. 1195 1196 The function returns a list where each element can be either a 1197 string (normal message) or a tuple of one or two strings (tagged 1198 messages). If a tuple has only one element (ie is a singleton), 1199 that element is the tag; otherwise the tuple has two elements: the 1200 tag and the data. 1201 1202 Arguments: 1203 1204 message -- The message to be decoded. 1205 """ 1206 1207 def _low_level_replace(match_obj): 1208 ch = match_obj.group(1) 1209 1210 # If low_level_mapping doesn't have the character as key, we 1211 # should just return the character. 1212 return _low_level_mapping.get(ch, ch) 1213 1214 if _LOW_LEVEL_QUOTE in message: 1215 # Yup, there was a quote. Release the dequoter, man! 1216 message = _low_level_regexp.sub(_low_level_replace, message) 1217 1218 if _CTCP_DELIMITER not in message: 1219 return [message] 1220 else: 1221 # Split it into parts. (Does any IRC client actually *use* 1222 # CTCP stacking like this?) 1223 chunks = message.split(_CTCP_DELIMITER) 1224 1225 messages = [] 1226 i = 0 1227 while i < len(chunks)-1: 1228 # Add message if it's non-empty. 1229 if len(chunks[i]) > 0: 1230 messages.append(chunks[i]) 1231 1232 if i < len(chunks)-2: 1233 # Aye! CTCP tagged data ahead! 1234 messages.append(tuple(chunks[i+1].split(" ", 1))) 1235 1236 i = i + 2 1237 1238 if len(chunks) % 2 == 0: 1239 # Hey, a lonely _CTCP_DELIMITER at the end! This means 1240 # that the last chunk, including the delimiter, is a 1241 # normal message! (This is according to the CTCP 1242 # specification.) 1243 messages.append(_CTCP_DELIMITER + chunks[-1]) 1244 1245 return messages 1246 1247def is_channel(string): 1248 """Check if a string is a channel name. 1249 1250 Returns true if the argument is a channel name, otherwise false. 1251 """ 1252 return string and string[0] in "#&+!" 1253 1254def ip_numstr_to_quad(num): 1255 """Convert an IP number as an integer given in ASCII 1256 representation (e.g. '3232235521') to an IP address string 1257 (e.g. '192.168.0.1').""" 1258 n = long(num) 1259 p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF, 1260 n >> 8 & 0xFF, n & 0xFF])) 1261 return ".".join(p) 1262 1263def ip_quad_to_numstr(quad): 1264 """Convert an IP address string (e.g. '192.168.0.1') to an IP 1265 number as an integer given in ASCII representation 1266 (e.g. '3232235521').""" 1267 p = map(long, quad.split(".")) 1268 s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]) 1269 if s[-1] == "L": 1270 s = s[:-1] 1271 return s 1272 1273def nm_to_n(s): 1274 """Get the nick part of a nickmask. 1275 1276 (The source of an Event is a nickmask.) 1277 """ 1278 return s.split("!")[0] 1279 1280def nm_to_uh(s): 1281 """Get the userhost part of a nickmask. 1282 1283 (The source of an Event is a nickmask.) 1284 """ 1285 return s.split("!")[1] 1286 1287def nm_to_h(s): 1288 """Get the host part of a nickmask. 1289 1290 (The source of an Event is a nickmask.) 1291 """ 1292 return s.split("@")[1] 1293 1294def nm_to_u(s): 1295 """Get the user part of a nickmask. 1296 1297 (The source of an Event is a nickmask.) 1298 """ 1299 s = s.split("!")[1] 1300 return s.split("@")[0] 1301 1302def parse_nick_modes(mode_string): 1303 """Parse a nick mode string. 1304 1305 The function returns a list of lists with three members: sign, 1306 mode and argument. The sign is \"+\" or \"-\". The argument is 1307 always None. 1308 1309 Example: 1310 1311 >>> irclib.parse_nick_modes(\"+ab-c\") 1312 [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]] 1313 """ 1314 1315 return _parse_modes(mode_string, "") 1316 1317def parse_channel_modes(mode_string): 1318 """Parse a channel mode string. 1319 1320 The function returns a list of lists with three members: sign, 1321 mode and argument. The sign is \"+\" or \"-\". The argument is 1322 None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\". 1323 1324 Example: 1325 1326 >>> irclib.parse_channel_modes(\"+ab-c foo\") 1327 [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]] 1328 """ 1329 1330 return _parse_modes(mode_string, "bklvo") 1331 1332def _parse_modes(mode_string, unary_modes=""): 1333 """[Internal]""" 1334 modes = [] 1335 arg_count = 0 1336 1337 # State variable. 1338 sign = "" 1339 1340 a = mode_string.split() 1341 if len(a) == 0: 1342 return [] 1343 else: 1344 mode_part, args = a[0], a[1:] 1345 1346 if mode_part[0] not in "+-": 1347 return [] 1348 for ch in mode_part: 1349 if ch in "+-": 1350 sign = ch 1351 elif ch == " ": 1352 collecting_arguments = 1 1353 elif ch in unary_modes: 1354 if len(args) >= arg_count + 1: 1355 modes.append([sign, ch, args[arg_count]]) 1356 arg_count = arg_count + 1 1357 else: 1358 modes.append([sign, ch, None]) 1359 else: 1360 modes.append([sign, ch, None]) 1361 return modes 1362 1363def _ping_ponger(connection, event): 1364 """[Internal]""" 1365 connection.pong(event.target()) 1366 1367# Numeric table mostly stolen from the Perl IRC module (Net::IRC). 1368numeric_events = { 1369 "001": "welcome", 1370 "002": "yourhost", 1371 "003": "created", 1372 "004": "myinfo", 1373 "005": "featurelist", # XXX 1374 "200": "tracelink", 1375 "201": "traceconnecting", 1376 "202": "tracehandshake", 1377 "203": "traceunknown", 1378 "204": "traceoperator", 1379 "205": "traceuser", 1380 "206": "traceserver", 1381 "207": "traceservice", 1382 "208": "tracenewtype", 1383 "209": "traceclass", 1384 "210": "tracereconnect", 1385 "211": "statslinkinfo", 1386 "212": "statscommands", 1387 "213": "statscline", 1388 "214": "statsnline", 1389 "215": "statsiline", 1390 "216": "statskline", 1391 "217": "statsqline", 1392 "218": "statsyline", 1393 "219": "endofstats", 1394 "221": "umodeis", 1395 "231": "serviceinfo", 1396 "232": "endofservices", 1397 "233": "service", 1398 "234": "servlist", 1399 "235": "servlistend", 1400 "241": "statslline", 1401 "242": "statsuptime", 1402 "243": "statsoline", 1403 "244": "statshline", 1404 "250": "luserconns", 1405 "251": "luserclient", 1406 "252": "luserop", 1407 "253": "luserunknown", 1408 "254": "luserchannels", 1409 "255": "luserme", 1410 "256": "adminme", 1411 "257": "adminloc1", 1412 "258": "adminloc2", 1413 "259": "adminemail", 1414 "261": "tracelog", 1415 "262": "endoftrace", 1416 "263": "tryagain", 1417 "265": "n_local", 1418 "266": "n_global", 1419 "300": "none", 1420 "301": "away", 1421 "302": "userhost", 1422 "303": "ison", 1423 "305": "unaway", 1424 "306": "nowaway", 1425 "311": "whoisuser", 1426 "312": "whoisserver", 1427 "313": "whoisoperator", 1428 "314": "whowasuser", 1429 "315": "endofwho", 1430 "316": "whoischanop", 1431 "317": "whoisidle", 1432 "318": "endofwhois", 1433 "319": "whoischannels", 1434 "321": "liststart", 1435 "322": "list", 1436 "323": "listend", 1437 "324": "channelmodeis", 1438 "329": "channelcreate", 1439 "331": "notopic", 1440 "332": "currenttopic", 1441 "333": "topicinfo", 1442 "341": "inviting", 1443 "342": "summoning", 1444 "346": "invitelist", 1445 "347": "endofinvitelist", 1446 "348": "exceptlist", 1447 "349": "endofexceptlist", 1448 "351": "version", 1449 "352": "whoreply", 1450 "353": "namreply", 1451 "361": "killdone", 1452 "362": "closing", 1453 "363": "closeend", 1454 "364": "links", 1455 "365": "endoflinks", 1456 "366": "endofnames", 1457 "367": "banlist", 1458 "368": "endofbanlist", 1459 "369": "endofwhowas", 1460 "371": "info", 1461 "372": "motd", 1462 "373": "infostart", 1463 "374": "endofinfo", 1464 "375": "motdstart", 1465 "376": "endofmotd", 1466 "377": "motd2", # 1997-10-16 -- tkil 1467 "381": "youreoper", 1468 "382": "rehashing", 1469 "384": "myportis", 1470 "391": "time", 1471 "392": "usersstart", 1472 "393": "users", 1473 "394": "endofusers", 1474 "395": "nousers", 1475 "401": "nosuchnick", 1476 "402": "nosuchserver", 1477 "403": "nosuchchannel", 1478 "404": "cannotsendtochan", 1479 "405": "toomanychannels", 1480 "406": "wasnosuchnick", 1481 "407": "toomanytargets", 1482 "409": "noorigin", 1483 "411": "norecipient", 1484 "412": "notexttosend", 1485 "413": "notoplevel", 1486 "414": "wildtoplevel", 1487 "421": "unknowncommand", 1488 "422": "nomotd", 1489 "423": "noadmininfo", 1490 "424": "fileerror", 1491 "431": "nonicknamegiven", 1492 "432": "erroneusnickname", # Thiss iz how its speld in thee RFC. 1493 "433": "nicknameinuse", 1494 "436": "nickcollision", 1495 "437": "unavailresource", # "Nick temporally unavailable" 1496 "441": "usernotinchannel", 1497 "442": "notonchannel", 1498 "443": "useronchannel", 1499 "444": "nologin", 1500 "445": "summondisabled", 1501 "446": "usersdisabled", 1502 "451": "notregistered", 1503 "461": "needmoreparams", 1504 "462": "alreadyregistered", 1505 "463": "nopermforhost", 1506 "464": "passwdmismatch", 1507 "465": "yourebannedcreep", # I love this one... 1508 "466": "youwillbebanned", 1509 "467": "keyset", 1510 "471": "channelisfull", 1511 "472": "unknownmode", 1512 "473": "inviteonlychan", 1513 "474": "bannedfromchan", 1514 "475": "badchannelkey", 1515 "476": "badchanmask", 1516 "477": "nochanmodes", # "Channel doesn't support modes" 1517 "478": "banlistfull", 1518 "481": "noprivileges", 1519 "482": "chanoprivsneeded", 1520 "483": "cantkillserver", 1521 "484": "restricted", # Connection is restricted 1522 "485": "uniqopprivsneeded", 1523 "491": "nooperhost", 1524 "492": "noservicehost", 1525 "501": "umodeunknownflag", 1526 "502": "usersdontmatch", 1527} 1528 1529generated_events = [ 1530 # Generated events 1531 "dcc_connect", 1532 "dcc_disconnect", 1533 "dccmsg", 1534 "disconnect", 1535 "ctcp", 1536 "ctcpreply", 1537] 1538 1539protocol_events = [ 1540 # IRC protocol events 1541 "error", 1542 "join", 1543 "kick", 1544 "mode", 1545 "part", 1546 "ping", 1547 "privmsg", 1548 "privnotice", 1549 "pubmsg", 1550 "pubnotice", 1551 "quit", 1552 "invite", 1553 "pong", 1554] 1555 1556all_events = generated_events + protocol_events + numeric_events.values()