1 # Copyright (C) 1999--2002 Joel Rosdahl
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.
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.
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
17 # keltus <keltus@users.sourceforge.net>
19 # $Id: irclib.py,v 1.43 2005/12/24 22:12:40 keltus Exp $
21 """irclib -- Internet Relay Chat (IRC) protocol client library.
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.
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].
32 The main features of the IRC client framework are:
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
39 * Messages from an IRC server triggers events, which can be caught
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
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.
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...
61 .. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
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
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.
91 class IRCError(Exception):
92 """Represents an IRC exception."""
97 """Class that handles one or several IRC server connections.
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.
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.
114 server = irc.server()
115 server.connect(\"irc.some.where\", 6667, \"my_nickname\")
116 server.privmsg(\"a_nickname\", \"Hi there!\")
117 irc.process_forever()
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.
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.
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
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.
146 An alternative is to just call ServerConnection.process_once()
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
154 self
.fn_to_add_socket
= None
155 self
.fn_to_remove_socket
= None
157 self
.fn_to_add_timeout
= fn_to_add_timeout
158 self
.connections
= []
160 self
.delayed_commands
= [] # list of tuples in the format (time, function, arguments)
162 self
.add_global_handler("ping", _ping_ponger
, -42)
165 """Creates and returns a ServerConnection object."""
167 c
= ServerConnection(self
)
168 self
.connections
.append(c
)
171 def process_data(self
, sockets
):
172 """Called when there is more data to read on connection sockets.
176 sockets -- A list of socket objects.
178 See documentation for IRC.__init__.
181 for c
in self
.connections
:
182 if s
== c
._get
_socket
():
185 def process_timeout(self
):
186 """Called when a timeout notification is due.
188 See documentation for IRC.__init__.
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]
198 def process_once(self
, timeout
=0):
199 """Process data from connections once.
203 timeout -- How long the select() call should wait if no
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.
210 sockets
= map(lambda x
: x
._get
_socket
(), self
.connections
)
211 sockets
= filter(lambda x
: x
!= None, sockets
)
214 (i
, o
, e
) = select
.select(sockets
, [], [], timeout
)
221 self
.process_timeout()
223 def process_forever(self
, timeout
=0.2):
224 """Run an infinite loop, processing data from connections.
226 This method repeatedly calls process_once.
230 timeout -- Parameter to pass to process_once.
233 self
.process_once(timeout
)
235 def disconnect_all(self
, message
=""):
236 """Disconnects all connections."""
237 for c
in self
.connections
:
238 c
.disconnect(message
)
240 def add_global_handler(self
, event
, handler
, priority
=0):
241 """Adds a global handler function for a specific event type.
245 event -- Event type (a string). Check the values of the
246 numeric_events dictionary in irclib.py for possible event
249 handler -- Callback function.
251 priority -- A number (the lower number, the higher priority).
253 The handler function is called whenever the specified event is
254 triggered in any of the connections. See documentation for
257 The handler functions are called in priority order (lowest
258 number is highest priority). If a handler function returns
259 \"NO MORE\", no more handlers will be called.
262 if not event
in self
.handlers
:
263 self
.handlers
[event
] = []
264 bisect
.insort(self
.handlers
[event
], ((priority
, handler
)))
266 def remove_global_handler(self
, event
, handler
):
267 """Removes a global handler function.
271 event -- Event type (a string).
273 handler -- Callback function.
275 Returns 1 on success, otherwise 0.
277 if not event
in self
.handlers
:
279 for h
in self
.handlers
[event
]:
281 self
.handlers
[event
].remove(h
)
284 def execute_at(self
, at
, function
, arguments
=()):
285 """Execute a function at a specified time.
289 at -- Execute at this time (standard \"time_t\" time).
291 function -- Function to call.
293 arguments -- Arguments to give the function.
295 self
.execute_delayed(at
-time
.time(), function
, arguments
)
297 def execute_delayed(self
, delay
, function
, arguments
=()):
298 """Execute a function after a specified time.
302 delay -- How many seconds to wait.
304 function -- Function to call.
306 arguments -- Arguments to give the function.
308 bisect
.insort(self
.delayed_commands
, (delay
+time
.time(), function
, arguments
))
309 if self
.fn_to_add_timeout
:
310 self
.fn_to_add_timeout(delay
)
312 def dcc(self
, dcctype
="chat"):
313 """Creates and returns a DCCConnection object.
317 dcctype -- "chat" for DCC CHAT connections or "raw" for
318 DCC SEND (or other DCC types). If "chat",
319 incoming data will be split in newline-separated
320 chunks. If "raw", incoming data is not touched.
322 c
= DCCConnection(self
, dcctype
)
323 self
.connections
.append(c
)
326 def _handle_event(self
, connection
, event
):
329 for handler
in h
.get("all_events", []) + h
.get(event
.eventtype(), []):
330 if handler
[1](connection
, event
) == "NO MORE":
333 def _remove_connection(self
, connection
):
335 self
.connections
.remove(connection
)
336 if self
.fn_to_remove_socket
:
337 self
.fn_to_remove_socket(connection
._get
_socket
())
339 _rfc_1459_command_regexp
= re
.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
343 """Base class for IRC connections.
347 def __init__(self
, irclibobj
):
348 self
.irclibobj
= irclibobj
351 raise IRCError
, "Not overridden"
353 ##############################
354 ### Convenience wrappers.
356 def execute_at(self
, at
, function
, arguments
=()):
357 self
.irclibobj
.execute_at(at
, function
, arguments
)
359 def execute_delayed(self
, delay
, function
, arguments
=()):
360 self
.irclibobj
.execute_delayed(delay
, function
, arguments
)
363 class ServerConnectionError(IRCError
):
366 class ServerNotConnectedError(ServerConnectionError
):
370 # Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
371 # use \n as message separator! :P
372 _linesep_regexp
= re
.compile("\r?\n")
374 class ServerConnection(Connection
):
375 """This class represents an IRC server connection.
377 ServerConnection objects are instantiated by calling the server
378 method on an IRC object.
381 def __init__(self
, irclibobj
):
382 Connection
.__init
__(self
, irclibobj
)
383 self
.connected
= 0 # Not connected yet.
386 def connect(self
, server
, port
, nickname
, password
=None, username
=None,
387 ircname
=None, localaddress
="", localport
=0):
388 """Connect/reconnect to a server.
392 server -- Server name.
396 nickname -- The nickname.
398 password -- Password (if any).
400 username -- The username.
402 ircname -- The IRC name ("realname").
404 localaddress -- Bind the connection to a specific local IP address.
406 localport -- Bind the connection to a specific local port.
408 This function can be called to reconnect a closed connection.
410 Returns the ServerConnection object.
413 self
.disconnect("Changing servers")
415 self
.previous_buffer
= ""
417 self
.real_server_name
= ""
418 self
.real_nickname
= nickname
421 self
.nickname
= nickname
422 self
.username
= username
or nickname
423 self
.ircname
= ircname
or nickname
424 self
.password
= password
425 self
.localaddress
= localaddress
426 self
.localport
= localport
427 self
.localhost
= socket
.gethostname()
428 self
.socket
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
430 self
.socket
.bind((self
.localaddress
, self
.localport
))
431 self
.socket
.connect((self
.server
, self
.port
))
432 except socket
.error
, x
:
435 raise ServerConnectionError
, "Couldn't connect to socket: %s" % x
437 if self
.irclibobj
.fn_to_add_socket
:
438 self
.irclibobj
.fn_to_add_socket(self
.socket
)
442 self
.pass_(self
.password
)
443 self
.nick(self
.nickname
)
444 self
.user(self
.username
, self
.ircname
)
448 """Close the connection.
450 This method closes the connection permanently; after it has
451 been called, the object is unusable.
454 self
.disconnect("Closing object")
455 self
.irclibobj
._remove
_connection
(self
)
457 def _get_socket(self
):
461 def get_server_name(self
):
462 """Get the (real) server name.
464 This method returns the (real) server name, or, more
465 specifically, what the server calls itself.
468 if self
.real_server_name
:
469 return self
.real_server_name
473 def get_nickname(self
):
474 """Get the (real) nick name.
476 This method returns the (real) nickname. The library keeps
477 track of nick changes, so it might not be the nick name that
478 was passed to the connect() method. """
480 return self
.real_nickname
482 def process_data(self
):
486 new_data
= self
.socket
.recv(2**14)
487 except socket
.error
, x
:
488 # The server hung up.
489 self
.disconnect("Connection reset by peer")
492 # Read nothing: connection must be down.
493 self
.disconnect("Connection reset by peer")
496 lines
= _linesep_regexp
.split(self
.previous_buffer
+ new_data
)
498 # Save the last, unfinished line.
499 self
.previous_buffer
= lines
[-1]
504 print "FROM SERVER:", line
512 self
._handle
_event
(Event("all_raw_messages",
513 self
.get_server_name(),
517 m
= _rfc_1459_command_regexp
.match(line
)
518 if m
.group("prefix"):
519 prefix
= m
.group("prefix")
520 if not self
.real_server_name
:
521 self
.real_server_name
= prefix
523 if m
.group("command"):
524 command
= m
.group("command").lower()
526 if m
.group("argument"):
527 a
= m
.group("argument").split(" :", 1)
528 arguments
= a
[0].split()
530 arguments
.append(a
[1])
532 # Translate numerics into more readable strings.
533 if command
in numeric_events
:
534 command
= numeric_events
[command
]
536 if command
== "nick":
537 if nm_to_n(prefix
) == self
.real_nickname
:
538 self
.real_nickname
= arguments
[0]
539 elif command
== "welcome":
540 # Record the nickname in case the client changed nick
541 # in a nicknameinuse callback.
542 self
.real_nickname
= arguments
[0]
544 if command
in ["privmsg", "notice"]:
545 target
, message
= arguments
[0], arguments
[1]
546 messages
= _ctcp_dequote(message
)
548 if command
== "privmsg":
549 if is_channel(target
):
552 if is_channel(target
):
553 command
= "pubnotice"
555 command
= "privnotice"
558 if type(m
) is types
.TupleType
:
559 if command
in ["privmsg", "pubmsg"]:
562 command
= "ctcpreply"
566 print "command: %s, source: %s, target: %s, arguments: %s" % (
567 command
, prefix
, target
, m
)
568 self
._handle
_event
(Event(command
, prefix
, target
, m
))
569 if command
== "ctcp" and m
[0] == "ACTION":
570 self
._handle
_event
(Event("action", prefix
, target
, m
[1:]))
573 print "command: %s, source: %s, target: %s, arguments: %s" % (
574 command
, prefix
, target
, [m
])
575 self
._handle
_event
(Event(command
, prefix
, target
, [m
]))
579 if command
== "quit":
580 arguments
= [arguments
[0]]
581 elif command
== "ping":
582 target
= arguments
[0]
584 target
= arguments
[0]
585 arguments
= arguments
[1:]
587 if command
== "mode":
588 if not is_channel(target
):
592 print "command: %s, source: %s, target: %s, arguments: %s" % (
593 command
, prefix
, target
, arguments
)
594 self
._handle
_event
(Event(command
, prefix
, target
, arguments
))
596 def _handle_event(self
, event
):
598 self
.irclibobj
._handle
_event
(self
, event
)
599 if event
.eventtype() in self
.handlers
:
600 for fn
in self
.handlers
[event
.eventtype()]:
603 def is_connected(self
):
604 """Return connection status.
606 Returns true if connected, otherwise false.
608 return self
.connected
610 def add_global_handler(self
, *args
):
611 """Add global handler.
613 See documentation for IRC.add_global_handler.
615 self
.irclibobj
.add_global_handler(*args
)
617 def remove_global_handler(self
, *args
):
618 """Remove global handler.
620 See documentation for IRC.remove_global_handler.
622 self
.irclibobj
.remove_global_handler(*args
)
624 def action(self
, target
, action
):
625 """Send a CTCP ACTION command."""
626 self
.ctcp("ACTION", target
, action
)
628 def admin(self
, server
=""):
629 """Send an ADMIN command."""
630 self
.send_raw(" ".join(["ADMIN", server
]).strip())
632 def ctcp(self
, ctcptype
, target
, parameter
=""):
633 """Send a CTCP command."""
634 ctcptype
= ctcptype
.upper()
635 self
.privmsg(target
, "\001%s%s\001" % (ctcptype
, parameter
and (" " + parameter
) or ""))
637 def ctcp_reply(self
, target
, parameter
):
638 """Send a CTCP REPLY command."""
639 self
.notice(target
, "\001%s\001" % parameter
)
641 def disconnect(self
, message
=""):
642 """Hang up the connection.
646 message -- Quit message.
648 if not self
.connected
:
657 except socket
.error
, x
:
660 self
._handle
_event
(Event("disconnect", self
.server
, "", [message
]))
662 def globops(self
, text
):
663 """Send a GLOBOPS command."""
664 self
.send_raw("GLOBOPS :" + text
)
666 def info(self
, server
=""):
667 """Send an INFO command."""
668 self
.send_raw(" ".join(["INFO", server
]).strip())
670 def invite(self
, nick
, channel
):
671 """Send an INVITE command."""
672 self
.send_raw(" ".join(["INVITE", nick
, channel
]).strip())
674 def ison(self
, nicks
):
675 """Send an ISON command.
679 nicks -- List of nicks.
681 self
.send_raw("ISON " + " ".join(nicks
))
683 def join(self
, channel
, key
=""):
684 """Send a JOIN command."""
685 self
.send_raw("JOIN %s%s" % (channel
, (key
and (" " + key
))))
687 def kick(self
, channel
, nick
, comment
=""):
688 """Send a KICK command."""
689 self
.send_raw("KICK %s %s%s" % (channel
, nick
, (comment
and (" :" + comment
))))
691 def links(self
, remote_server
="", server_mask
=""):
692 """Send a LINKS command."""
695 command
= command
+ " " + remote_server
697 command
= command
+ " " + server_mask
698 self
.send_raw(command
)
700 def list(self
, channels
=None, server
=""):
701 """Send a LIST command."""
704 command
= command
+ " " + ",".join(channels
)
706 command
= command
+ " " + server
707 self
.send_raw(command
)
709 def lusers(self
, server
=""):
710 """Send a LUSERS command."""
711 self
.send_raw("LUSERS" + (server
and (" " + server
)))
713 def mode(self
, target
, command
):
714 """Send a MODE command."""
715 self
.send_raw("MODE %s %s" % (target
, command
))
717 def motd(self
, server
=""):
718 """Send an MOTD command."""
719 self
.send_raw("MOTD" + (server
and (" " + server
)))
721 def names(self
, channels
=None):
722 """Send a NAMES command."""
723 self
.send_raw("NAMES" + (channels
and (" " + ",".join(channels
)) or ""))
725 def nick(self
, newnick
):
726 """Send a NICK command."""
727 self
.send_raw("NICK " + newnick
)
729 def notice(self
, target
, text
):
730 """Send a NOTICE command."""
731 # Should limit len(text) here!
732 self
.send_raw("NOTICE %s :%s" % (target
, text
))
734 def oper(self
, nick
, password
):
735 """Send an OPER command."""
736 self
.send_raw("OPER %s %s" % (nick
, password
))
738 def part(self
, channels
, message
=""):
739 """Send a PART command."""
740 if type(channels
) == types
.StringType
:
741 self
.send_raw("PART " + channels
+ (message
and (" " + message
)))
743 self
.send_raw("PART " + ",".join(channels
) + (message
and (" " + message
)))
745 def pass_(self
, password
):
746 """Send a PASS command."""
747 self
.send_raw("PASS " + password
)
749 def ping(self
, target
, target2
=""):
750 """Send a PING command."""
751 self
.send_raw("PING %s%s" % (target
, target2
and (" " + target2
)))
753 def pong(self
, target
, target2
=""):
754 """Send a PONG command."""
755 self
.send_raw("PONG %s%s" % (target
, target2
and (" " + target2
)))
757 def privmsg(self
, target
, text
):
758 """Send a PRIVMSG command."""
759 # Should limit len(text) here!
760 self
.send_raw("PRIVMSG %s :%s" % (target
, text
))
762 def privmsg_many(self
, targets
, text
):
763 """Send a PRIVMSG command to multiple targets."""
764 # Should limit len(text) here!
765 self
.send_raw("PRIVMSG %s :%s" % (",".join(targets
), text
))
767 def quit(self
, message
=""):
768 """Send a QUIT command."""
769 # Note that many IRC servers don't use your QUIT message
770 # unless you've been connected for at least 5 minutes!
771 self
.send_raw("QUIT" + (message
and (" :" + message
)))
773 def sconnect(self
, target
, port
="", server
=""):
774 """Send an SCONNECT command."""
775 self
.send_raw("CONNECT %s%s%s" % (target
,
776 port
and (" " + port
),
777 server
and (" " + server
)))
779 def send_raw(self
, string
):
780 """Send raw string to the server.
782 The string will be padded with appropriate CR LF.
784 if self
.socket
is None:
785 raise ServerNotConnectedError
, "Not connected."
787 self
.socket
.send(string
+ "\r\n")
789 print "TO SERVER:", string
790 except socket
.error
, x
:
792 self
.disconnect("Connection reset by peer.")
794 def squit(self
, server
, comment
=""):
795 """Send an SQUIT command."""
796 self
.send_raw("SQUIT %s%s" % (server
, comment
and (" :" + comment
)))
798 def stats(self
, statstype
, server
=""):
799 """Send a STATS command."""
800 self
.send_raw("STATS %s%s" % (statstype
, server
and (" " + server
)))
802 def time(self
, server
=""):
803 """Send a TIME command."""
804 self
.send_raw("TIME" + (server
and (" " + server
)))
806 def topic(self
, channel
, new_topic
=None):
807 """Send a TOPIC command."""
808 if new_topic
is None:
809 self
.send_raw("TOPIC " + channel
)
811 self
.send_raw("TOPIC %s :%s" % (channel
, new_topic
))
813 def trace(self
, target
=""):
814 """Send a TRACE command."""
815 self
.send_raw("TRACE" + (target
and (" " + target
)))
817 def user(self
, username
, realname
):
818 """Send a USER command."""
819 self
.send_raw("USER %s 0 * :%s" % (username
, realname
))
821 def userhost(self
, nicks
):
822 """Send a USERHOST command."""
823 self
.send_raw("USERHOST " + ",".join(nicks
))
825 def users(self
, server
=""):
826 """Send a USERS command."""
827 self
.send_raw("USERS" + (server
and (" " + server
)))
829 def version(self
, server
=""):
830 """Send a VERSION command."""
831 self
.send_raw("VERSION" + (server
and (" " + server
)))
833 def wallops(self
, text
):
834 """Send a WALLOPS command."""
835 self
.send_raw("WALLOPS :" + text
)
837 def who(self
, target
="", op
=""):
838 """Send a WHO command."""
839 self
.send_raw("WHO%s%s" % (target
and (" " + target
), op
and (" o")))
841 def whois(self
, targets
):
842 """Send a WHOIS command."""
843 self
.send_raw("WHOIS " + ",".join(targets
))
845 def whowas(self
, nick
, max="", server
=""):
846 """Send a WHOWAS command."""
847 self
.send_raw("WHOWAS %s%s%s" % (nick
,
849 server
and (" " + server
)))
852 class DCCConnectionError(IRCError
):
856 class DCCConnection(Connection
):
857 """This class represents a DCC connection.
859 DCCConnection objects are instantiated by calling the dcc
860 method on an IRC object.
862 def __init__(self
, irclibobj
, dcctype
):
863 Connection
.__init
__(self
, irclibobj
)
866 self
.dcctype
= dcctype
867 self
.peeraddress
= None
870 def connect(self
, address
, port
):
871 """Connect/reconnect to a DCC peer.
874 address -- Host/IP address of the peer.
876 port -- The port number to connect to.
878 Returns the DCCConnection object.
880 self
.peeraddress
= socket
.gethostbyname(address
)
883 self
.previous_buffer
= ""
885 self
.socket
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
888 self
.socket
.connect((self
.peeraddress
, self
.peerport
))
889 except socket
.error
, x
:
890 raise DCCConnectionError
, "Couldn't connect to socket: %s" % x
892 if self
.irclibobj
.fn_to_add_socket
:
893 self
.irclibobj
.fn_to_add_socket(self
.socket
)
897 """Wait for a connection/reconnection from a DCC peer.
899 Returns the DCCConnection object.
901 The local IP address and port are available as
902 self.localaddress and self.localport. After connection from a
903 peer, the peer address and port are available as
904 self.peeraddress and self.peerport.
906 self
.previous_buffer
= ""
908 self
.socket
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
911 self
.socket
.bind((socket
.gethostbyname(socket
.gethostname()), 0))
912 self
.localaddress
, self
.localport
= self
.socket
.getsockname()
913 self
.socket
.listen(10)
914 except socket
.error
, x
:
915 raise DCCConnectionError
, "Couldn't bind socket: %s" % x
918 def disconnect(self
, message
=""):
919 """Hang up the connection and close the object.
923 message -- Quit message.
925 if not self
.connected
:
931 except socket
.error
, x
:
934 self
.irclibobj
._handle
_event
(
936 Event("dcc_disconnect", self
.peeraddress
, "", [message
]))
937 self
.irclibobj
._remove
_connection
(self
)
939 def process_data(self
):
942 if self
.passive
and not self
.connected
:
943 conn
, (self
.peeraddress
, self
.peerport
) = self
.socket
.accept()
948 print "DCC connection from %s:%d" % (
949 self
.peeraddress
, self
.peerport
)
950 self
.irclibobj
._handle
_event
(
952 Event("dcc_connect", self
.peeraddress
, None, None))
956 new_data
= self
.socket
.recv(2**14)
957 except socket
.error
, x
:
958 # The server hung up.
959 self
.disconnect("Connection reset by peer")
962 # Read nothing: connection must be down.
963 self
.disconnect("Connection reset by peer")
966 if self
.dcctype
== "chat":
967 # The specification says lines are terminated with LF, but
968 # it seems safer to handle CR LF terminations too.
969 chunks
= _linesep_regexp
.split(self
.previous_buffer
+ new_data
)
971 # Save the last, unfinished line.
972 self
.previous_buffer
= chunks
[-1]
973 if len(self
.previous_buffer
) > 2**14:
974 # Bad peer! Naughty peer!
982 prefix
= self
.peeraddress
986 print "FROM PEER:", chunk
989 print "command: %s, source: %s, target: %s, arguments: %s" % (
990 command
, prefix
, target
, arguments
)
991 self
.irclibobj
._handle
_event
(
993 Event(command
, prefix
, target
, arguments
))
995 def _get_socket(self
):
999 def privmsg(self
, string
):
1000 """Send data to DCC peer.
1002 The string will be padded with appropriate LF if it's a DCC
1006 self
.socket
.send(string
)
1007 if self
.dcctype
== "chat":
1008 self
.socket
.send("\n")
1010 print "TO PEER: %s\n" % string
1011 except socket
.error
, x
:
1013 self
.disconnect("Connection reset by peer.")
1015 class SimpleIRCClient
:
1016 """A simple single-server IRC client class.
1018 This is an example of an object-oriented wrapper of the IRC
1019 framework. A real IRC client can be made by subclassing this
1020 class and adding appropriate methods.
1022 The method on_join will be called when a "join" event is created
1023 (which is done when the server sends a JOIN messsage/command),
1024 on_privmsg will be called for "privmsg" events, and so on. The
1025 handler methods get two arguments: the connection object (same as
1026 self.connection) and the event object.
1028 Instance attributes that can be used by sub classes:
1030 ircobj -- The IRC instance.
1032 connection -- The ServerConnection instance.
1034 dcc_connections -- A list of DCCConnection instances.
1038 self
.connection
= self
.ircobj
.server()
1039 self
.dcc_connections
= []
1040 self
.ircobj
.add_global_handler("all_events", self
._dispatcher
, -10)
1041 self
.ircobj
.add_global_handler("dcc_disconnect", self
._dcc
_disconnect
, -10)
1043 def _dispatcher(self
, c
, e
):
1045 m
= "on_" + e
.eventtype()
1046 if hasattr(self
, m
):
1047 getattr(self
, m
)(c
, e
)
1049 def _dcc_disconnect(self
, c
, e
):
1050 self
.dcc_connections
.remove(c
)
1052 def connect(self
, server
, port
, nickname
, password
=None, username
=None,
1053 ircname
=None, localaddress
="", localport
=0):
1054 """Connect/reconnect to a server.
1058 server -- Server name.
1060 port -- Port number.
1062 nickname -- The nickname.
1064 password -- Password (if any).
1066 username -- The username.
1068 ircname -- The IRC name.
1070 localaddress -- Bind the connection to a specific local IP address.
1072 localport -- Bind the connection to a specific local port.
1074 This function can be called to reconnect a closed connection.
1076 self
.connection
.connect(server
, port
, nickname
,
1077 password
, username
, ircname
,
1078 localaddress
, localport
)
1080 def dcc_connect(self
, address
, port
, dcctype
="chat"):
1081 """Connect to a DCC peer.
1085 address -- IP address of the peer.
1087 port -- Port to connect to.
1089 Returns a DCCConnection instance.
1091 dcc
= self
.ircobj
.dcc(dcctype
)
1092 self
.dcc_connections
.append(dcc
)
1093 dcc
.connect(address
, port
)
1096 def dcc_listen(self
, dcctype
="chat"):
1097 """Listen for connections from a DCC peer.
1099 Returns a DCCConnection instance.
1101 dcc
= self
.ircobj
.dcc(dcctype
)
1102 self
.dcc_connections
.append(dcc
)
1107 """Start the IRC client."""
1108 self
.ircobj
.process_forever()
1112 """Class representing an IRC event."""
1113 def __init__(self
, eventtype
, source
, target
, arguments
=None):
1114 """Constructor of Event objects.
1118 eventtype -- A string describing the event.
1120 source -- The originator of the event (a nick mask or a server).
1122 target -- The target of the event (a nick or a channel).
1124 arguments -- Any event specific arguments.
1126 self
._eventtype
= eventtype
1127 self
._source
= source
1128 self
._target
= target
1130 self
._arguments
= arguments
1132 self
._arguments
= []
1134 def eventtype(self
):
1135 """Get the event type."""
1136 return self
._eventtype
1139 """Get the event source."""
1143 """Get the event target."""
1146 def arguments(self
):
1147 """Get the event arguments."""
1148 return self
._arguments
1150 _LOW_LEVEL_QUOTE
= "\020"
1151 _CTCP_LEVEL_QUOTE
= "\134"
1152 _CTCP_DELIMITER
= "\001"
1154 _low_level_mapping
= {
1158 _LOW_LEVEL_QUOTE
: _LOW_LEVEL_QUOTE
1161 _low_level_regexp
= re
.compile(_LOW_LEVEL_QUOTE
+ "(.)")
1163 def mask_matches(nick
, mask
):
1164 """Check if a nick matches a mask.
1166 Returns true if the nick matches, otherwise false.
1168 nick
= irc_lower(nick
)
1169 mask
= irc_lower(mask
)
1170 mask
= mask
.replace("\\", "\\\\")
1171 for ch
in ".$|[](){}+":
1172 mask
= mask
.replace(ch
, "\\" + ch
)
1173 mask
= mask
.replace("?", ".")
1174 mask
= mask
.replace("*", ".*")
1175 r
= re
.compile(mask
, re
.IGNORECASE
)
1176 return r
.match(nick
)
1178 _special
= "-[]\\`^{}"
1179 nick_characters
= string
.ascii_letters
+ string
.digits
+ _special
1180 _ircstring_translation
= string
.maketrans(string
.ascii_uppercase
+ "[]\\^",
1181 string
.ascii_lowercase
+ "{}|~")
1184 """Returns a lowercased string.
1186 The definition of lowercased comes from the IRC specification (RFC
1189 return s
.translate(_ircstring_translation
)
1191 def _ctcp_dequote(message
):
1192 """[Internal] Dequote a message according to CTCP specifications.
1194 The function returns a list where each element can be either a
1195 string (normal message) or a tuple of one or two strings (tagged
1196 messages). If a tuple has only one element (ie is a singleton),
1197 that element is the tag; otherwise the tuple has two elements: the
1202 message -- The message to be decoded.
1205 def _low_level_replace(match_obj
):
1206 ch
= match_obj
.group(1)
1208 # If low_level_mapping doesn't have the character as key, we
1209 # should just return the character.
1210 return _low_level_mapping
.get(ch
, ch
)
1212 if _LOW_LEVEL_QUOTE
in message
:
1213 # Yup, there was a quote. Release the dequoter, man!
1214 message
= _low_level_regexp
.sub(_low_level_replace
, message
)
1216 if _CTCP_DELIMITER
not in message
:
1219 # Split it into parts. (Does any IRC client actually *use*
1220 # CTCP stacking like this?)
1221 chunks
= message
.split(_CTCP_DELIMITER
)
1225 while i
< len(chunks
)-1:
1226 # Add message if it's non-empty.
1227 if len(chunks
[i
]) > 0:
1228 messages
.append(chunks
[i
])
1230 if i
< len(chunks
)-2:
1231 # Aye! CTCP tagged data ahead!
1232 messages
.append(tuple(chunks
[i
+1].split(" ", 1)))
1236 if len(chunks
) % 2 == 0:
1237 # Hey, a lonely _CTCP_DELIMITER at the end! This means
1238 # that the last chunk, including the delimiter, is a
1239 # normal message! (This is according to the CTCP
1241 messages
.append(_CTCP_DELIMITER
+ chunks
[-1])
1245 def is_channel(string
):
1246 """Check if a string is a channel name.
1248 Returns true if the argument is a channel name, otherwise false.
1250 return string
and string
[0] in "#&+!"
1252 def ip_numstr_to_quad(num
):
1253 """Convert an IP number as an integer given in ASCII
1254 representation (e.g. '3232235521') to an IP address string
1255 (e.g. '192.168.0.1')."""
1257 p
= map(str, map(int, [n
>> 24 & 0xFF, n
>> 16 & 0xFF,
1258 n
>> 8 & 0xFF, n
& 0xFF]))
1261 def ip_quad_to_numstr(quad
):
1262 """Convert an IP address string (e.g. '192.168.0.1') to an IP
1263 number as an integer given in ASCII representation
1264 (e.g. '3232235521')."""
1265 p
= map(long, quad
.split("."))
1266 s
= str((p
[0] << 24) |
(p
[1] << 16) |
(p
[2] << 8) | p
[3])
1272 """Get the nick part of a nickmask.
1274 (The source of an Event is a nickmask.)
1276 return s
.split("!")[0]
1279 """Get the userhost part of a nickmask.
1281 (The source of an Event is a nickmask.)
1283 return s
.split("!")[1]
1286 """Get the host part of a nickmask.
1288 (The source of an Event is a nickmask.)
1290 return s
.split("@")[1]
1293 """Get the user part of a nickmask.
1295 (The source of an Event is a nickmask.)
1298 return s
.split("@")[0]
1300 def parse_nick_modes(mode_string
):
1301 """Parse a nick mode string.
1303 The function returns a list of lists with three members: sign,
1304 mode and argument. The sign is \"+\" or \"-\". The argument is
1309 >>> irclib.parse_nick_modes(\"+ab-c\")
1310 [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1313 return _parse_modes(mode_string
, "")
1315 def parse_channel_modes(mode_string
):
1316 """Parse a channel mode string.
1318 The function returns a list of lists with three members: sign,
1319 mode and argument. The sign is \"+\" or \"-\". The argument is
1320 None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
1324 >>> irclib.parse_channel_modes(\"+ab-c foo\")
1325 [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1328 return _parse_modes(mode_string
, "bklvhoaq")
1330 def _parse_modes(mode_string
, unary_modes
=""):
1338 a
= mode_string
.split()
1342 mode_part
, args
= a
[0], a
[1:]
1344 if mode_part
[0] not in "+-":
1346 for ch
in mode_part
:
1350 collecting_arguments
= 1
1351 elif ch
in unary_modes
:
1352 if len(args
) >= arg_count
+ 1:
1353 modes
.append([sign
, ch
, args
[arg_count
]])
1354 arg_count
= arg_count
+ 1
1356 modes
.append([sign
, ch
, None])
1358 modes
.append([sign
, ch
, None])
1361 def _ping_ponger(connection
, event
):
1363 connection
.pong(event
.target())
1365 # Numeric table mostly stolen from the Perl IRC module (Net::IRC).
1371 "005": "featurelist", # XXX
1373 "201": "traceconnecting",
1374 "202": "tracehandshake",
1375 "203": "traceunknown",
1376 "204": "traceoperator",
1378 "206": "traceserver",
1379 "207": "traceservice",
1380 "208": "tracenewtype",
1381 "209": "traceclass",
1382 "210": "tracereconnect",
1383 "211": "statslinkinfo",
1384 "212": "statscommands",
1385 "213": "statscline",
1386 "214": "statsnline",
1387 "215": "statsiline",
1388 "216": "statskline",
1389 "217": "statsqline",
1390 "218": "statsyline",
1391 "219": "endofstats",
1393 "231": "serviceinfo",
1394 "232": "endofservices",
1397 "235": "servlistend",
1398 "241": "statslline",
1399 "242": "statsuptime",
1400 "243": "statsoline",
1401 "244": "statshline",
1402 "250": "luserconns",
1403 "251": "luserclient",
1405 "253": "luserunknown",
1406 "254": "luserchannels",
1411 "259": "adminemail",
1413 "262": "endoftrace",
1424 "312": "whoisserver",
1425 "313": "whoisoperator",
1426 "314": "whowasuser",
1428 "316": "whoischanop",
1430 "318": "endofwhois",
1431 "319": "whoischannels",
1435 "324": "channelmodeis",
1436 "329": "channelcreate",
1438 "332": "currenttopic",
1442 "346": "invitelist",
1443 "347": "endofinvitelist",
1444 "348": "exceptlist",
1445 "349": "endofexceptlist",
1453 "365": "endoflinks",
1454 "366": "endofnames",
1456 "368": "endofbanlist",
1457 "369": "endofwhowas",
1464 "377": "motd2", # 1997-10-16 -- tkil
1469 "392": "usersstart",
1471 "394": "endofusers",
1473 "401": "nosuchnick",
1474 "402": "nosuchserver",
1475 "403": "nosuchchannel",
1476 "404": "cannotsendtochan",
1477 "405": "toomanychannels",
1478 "406": "wasnosuchnick",
1479 "407": "toomanytargets",
1481 "411": "norecipient",
1482 "412": "notexttosend",
1483 "413": "notoplevel",
1484 "414": "wildtoplevel",
1485 "421": "unknowncommand",
1487 "423": "noadmininfo",
1489 "431": "nonicknamegiven",
1490 "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
1491 "433": "nicknameinuse",
1492 "436": "nickcollision",
1493 "437": "unavailresource", # "Nick temporally unavailable"
1494 "441": "usernotinchannel",
1495 "442": "notonchannel",
1496 "443": "useronchannel",
1498 "445": "summondisabled",
1499 "446": "usersdisabled",
1500 "451": "notregistered",
1501 "461": "needmoreparams",
1502 "462": "alreadyregistered",
1503 "463": "nopermforhost",
1504 "464": "passwdmismatch",
1505 "465": "yourebannedcreep", # I love this one...
1506 "466": "youwillbebanned",
1508 "471": "channelisfull",
1509 "472": "unknownmode",
1510 "473": "inviteonlychan",
1511 "474": "bannedfromchan",
1512 "475": "badchannelkey",
1513 "476": "badchanmask",
1514 "477": "nochanmodes", # "Channel doesn't support modes"
1515 "478": "banlistfull",
1516 "481": "noprivileges",
1517 "482": "chanoprivsneeded",
1518 "483": "cantkillserver",
1519 "484": "restricted", # Connection is restricted
1520 "485": "uniqopprivsneeded",
1521 "491": "nooperhost",
1522 "492": "noservicehost",
1523 "501": "umodeunknownflag",
1524 "502": "usersdontmatch",
1527 generated_events
= [
1538 # IRC protocol events
1554 all_events
= generated_events
+ protocol_events
+ numeric_events
.values()