]>
gitweb.pimeys.fr Git - python-myirclib.git/blob - irclib.py
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.47 2008/09/25 22:00:59 keltus Exp $
22 # Fix added by Vincent Le Gallic <legallic@rans.org>
23 # Needed that socket communication doesn't crash
24 # on receiving SIGHUP, so added a decorator where I needed it
25 # the code of the decorator is in safesystemcall
27 """irclib -- Internet Relay Chat (IRC) protocol client library.
29 This library is intended to encapsulate the IRC protocol at a quite
30 low level. It provides an event-driven IRC client framework. It has
31 a fairly thorough support for the basic IRC protocol, CTCP, DCC chat,
32 but DCC file transfers is not yet supported.
34 In order to understand how to make an IRC client, I'm afraid you more
35 or less must understand the IRC specifications. They are available
36 here: [IRC specifications].
38 The main features of the IRC client framework are:
40 * Abstraction of the IRC protocol.
41 * Handles multiple simultaneous IRC server connections.
42 * Handles server PONGing transparently.
43 * Messages to the IRC server are done by calling methods on an IRC
45 * Messages from an IRC server triggers events, which can be caught
47 * Reading from and writing to IRC server sockets are normally done
48 by an internal select() loop, but the select()ing may be done by
49 an external main loop.
50 * Functions can be registered to execute at specified times by the
52 * Decodes CTCP tagging correctly (hopefully); I haven't seen any
53 other IRC client implementation that handles the CTCP
54 specification subtilties.
55 * A kind of simple, single-server, object-oriented IRC client class
56 that dispatches events to instance methods is included.
60 * The IRC protocol shines through the abstraction a bit too much.
61 * Data is not written asynchronously to the server, i.e. the write()
62 may block if the TCP buffers are stuffed.
63 * There are no support for DCC file transfers.
64 * The author haven't even read RFC 2810, 2811, 2812 and 2813.
65 * Like most projects, documentation is lacking...
67 .. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
80 thisfile
= os
.path
.realpath(__file__
)
81 thisdirectory
= thisfile
.rsplit("/", 1)[0]
82 sys
.path
.append(thisdirectory
)
90 # (maybe) thread safety
91 # (maybe) color parser convenience functions
92 # documentation (including all event types)
93 # (maybe) add awareness of different types of ircds
94 # send data asynchronously to the server (and DCC connections)
95 # (maybe) automatically close unused, passive DCC connections after a while
99 # connection.quit() only sends QUIT to the server.
100 # ERROR from the server triggers the error event and the disconnect event.
101 # dropping of the connection triggers the disconnect event.
103 class IRCError(Exception):
104 """Represents an IRC exception."""
109 """Class that handles one or several IRC server connections.
111 When an IRC object has been instantiated, it can be used to create
112 Connection objects that represent the IRC connections. The
113 responsibility of the IRC object is to provide an event-driven
114 framework for the connections and to keep the connections alive.
115 It runs a select loop to poll each connection's TCP socket and
116 hands over the sockets with incoming data for processing by the
117 corresponding connection.
119 The methods of most interest for an IRC client writer are server,
120 add_global_handler, remove_global_handler, execute_at,
121 execute_delayed, process_once and process_forever.
126 server = irc.server()
127 server.connect(\"irc.some.where\", 6667, \"my_nickname\")
128 server.privmsg(\"a_nickname\", \"Hi there!\")
129 irc.process_forever()
131 This will connect to the IRC server irc.some.where on port 6667
132 using the nickname my_nickname and send the message \"Hi there!\"
133 to the nickname a_nickname.
136 def __init__(self
, fn_to_add_socket
=None,
137 fn_to_remove_socket
=None,
138 fn_to_add_timeout
=None):
139 """Constructor for IRC objects.
141 Optional arguments are fn_to_add_socket, fn_to_remove_socket
142 and fn_to_add_timeout. The first two specify functions that
143 will be called with a socket object as argument when the IRC
144 object wants to be notified (or stop being notified) of data
145 coming on a new socket. When new data arrives, the method
146 process_data should be called. Similarly, fn_to_add_timeout
147 is called with a number of seconds (a floating point number)
148 as first argument when the IRC object wants to receive a
149 notification (by calling the process_timeout method). So, if
150 e.g. the argument is 42.17, the object wants the
151 process_timeout method to be called after 42 seconds and 170
154 The three arguments mainly exist to be able to use an external
155 main loop (for example Tkinter's or PyGTK's main app loop)
156 instead of calling the process_forever method.
158 An alternative is to just call ServerConnection.process_once()
162 if fn_to_add_socket
and fn_to_remove_socket
:
163 self
.fn_to_add_socket
= fn_to_add_socket
164 self
.fn_to_remove_socket
= fn_to_remove_socket
166 self
.fn_to_add_socket
= None
167 self
.fn_to_remove_socket
= None
169 self
.fn_to_add_timeout
= fn_to_add_timeout
170 self
.connections
= []
172 self
.delayed_commands
= [] # list of tuples in the format (time, function, arguments)
174 self
.add_global_handler("ping", _ping_ponger
, -42)
177 """Creates and returns a ServerConnection object."""
179 c
= ServerConnection(self
)
180 self
.connections
.append(c
)
183 def process_data(self
, sockets
):
184 """Called when there is more data to read on connection sockets.
188 sockets -- A list of socket objects.
190 See documentation for IRC.__init__.
193 for c
in self
.connections
:
194 if s
== c
._get
_socket
():
197 def process_timeout(self
):
198 """Called when a timeout notification is due.
200 See documentation for IRC.__init__.
203 while self
.delayed_commands
:
204 if t
>= self
.delayed_commands
[0][0]:
205 self
.delayed_commands
[0][1](*self
.delayed_commands
[0][2])
206 del self
.delayed_commands
[0]
210 @safesystemcall.systemcall
211 def process_once(self
, timeout
=0):
212 """Process data from connections once.
216 timeout -- How long the select() call should wait if no
219 This method should be called periodically to check and process
220 incoming data, if there are any. If that seems boring, look
221 at the process_forever method.
223 sockets
= map(lambda x
: x
._get
_socket
(), self
.connections
)
224 sockets
= filter(lambda x
: x
!= None, sockets
)
226 (i
, o
, e
) = select
.select(sockets
, [], [], timeout
)
230 self
.process_timeout()
232 def process_forever(self
, timeout
=0.2):
233 """Run an infinite loop, processing data from connections.
235 This method repeatedly calls process_once.
239 timeout -- Parameter to pass to process_once.
242 self
.process_once(timeout
)
244 def disconnect_all(self
, message
=""):
245 """Disconnects all connections."""
246 for c
in self
.connections
:
247 c
.disconnect(message
)
249 def add_global_handler(self
, event
, handler
, priority
=0):
250 """Adds a global handler function for a specific event type.
254 event -- Event type (a string). Check the values of the
255 numeric_events dictionary in irclib.py for possible event
258 handler -- Callback function.
260 priority -- A number (the lower number, the higher priority).
262 The handler function is called whenever the specified event is
263 triggered in any of the connections. See documentation for
266 The handler functions are called in priority order (lowest
267 number is highest priority). If a handler function returns
268 \"NO MORE\", no more handlers will be called.
270 if not event
in self
.handlers
:
271 self
.handlers
[event
] = []
272 bisect
.insort(self
.handlers
[event
], ((priority
, handler
)))
274 def remove_global_handler(self
, event
, handler
):
275 """Removes a global handler function.
279 event -- Event type (a string).
281 handler -- Callback function.
283 Returns 1 on success, otherwise 0.
285 if not event
in self
.handlers
:
287 for h
in self
.handlers
[event
]:
289 self
.handlers
[event
].remove(h
)
292 def execute_at(self
, at
, function
, arguments
=()):
293 """Execute a function at a specified time.
297 at -- Execute at this time (standard \"time_t\" time).
299 function -- Function to call.
301 arguments -- Arguments to give the function.
303 self
.execute_delayed(at
-time
.time(), function
, arguments
)
305 def execute_delayed(self
, delay
, function
, arguments
=()):
306 """Execute a function after a specified time.
310 delay -- How many seconds to wait.
312 function -- Function to call.
314 arguments -- Arguments to give the function.
316 bisect
.insort(self
.delayed_commands
, (delay
+time
.time(), function
, arguments
))
317 if self
.fn_to_add_timeout
:
318 self
.fn_to_add_timeout(delay
)
320 def dcc(self
, dcctype
="chat"):
321 """Creates and returns a DCCConnection object.
325 dcctype -- "chat" for DCC CHAT connections or "raw" for
326 DCC SEND (or other DCC types). If "chat",
327 incoming data will be split in newline-separated
328 chunks. If "raw", incoming data is not touched.
330 c
= DCCConnection(self
, dcctype
)
331 self
.connections
.append(c
)
334 def _handle_event(self
, connection
, event
):
337 for handler
in h
.get("all_events", []) + h
.get(event
.eventtype(), []):
338 if handler
[1](connection
, event
) == "NO MORE":
341 def _remove_connection(self
, connection
):
343 self
.connections
.remove(connection
)
344 if self
.fn_to_remove_socket
:
345 self
.fn_to_remove_socket(connection
._get
_socket
())
347 _rfc_1459_command_regexp
= re
.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
350 """Base class for IRC connections.
354 def __init__(self
, irclibobj
):
355 self
.irclibobj
= irclibobj
358 raise IRCError
, "Not overridden"
360 ##############################
361 ### Convenience wrappers.
363 def execute_at(self
, at
, function
, arguments
=()):
364 self
.irclibobj
.execute_at(at
, function
, arguments
)
366 def execute_delayed(self
, delay
, function
, arguments
=()):
367 self
.irclibobj
.execute_delayed(delay
, function
, arguments
)
370 class ServerConnectionError(IRCError
):
373 class ServerNotConnectedError(ServerConnectionError
):
377 # Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
378 # use \n as message separator! :P
379 _linesep_regexp
= re
.compile("\r?\n")
381 class ServerConnection(Connection
):
382 """This class represents an IRC server connection.
384 ServerConnection objects are instantiated by calling the server
385 method on an IRC object.
388 def __init__(self
, irclibobj
):
389 Connection
.__init
__(self
, irclibobj
)
390 self
.connected
= 0 # Not connected yet.
394 def connect(self
, server
, port
, nickname
, password
=None, username
=None,
395 ircname
=None, localaddress
="", localport
=0, ssl
=False, ipv6
=False):
396 """Connect/reconnect to a server.
400 server -- Server name.
404 nickname -- The nickname.
406 password -- Password (if any).
408 username -- The username.
410 ircname -- The IRC name ("realname").
412 localaddress -- Bind the connection to a specific local IP address.
414 localport -- Bind the connection to a specific local port.
416 ssl -- Enable support for ssl.
418 ipv6 -- Enable support for ipv6.
420 This function can be called to reconnect a closed connection.
422 Returns the ServerConnection object.
425 self
.disconnect("Changing servers")
427 self
.previous_buffer
= ""
429 self
.real_server_name
= ""
430 self
.real_nickname
= nickname
433 self
.nickname
= nickname
434 self
.username
= username
or nickname
435 self
.ircname
= ircname
or nickname
436 self
.password
= password
437 self
.localaddress
= localaddress
438 self
.localport
= localport
439 self
.localhost
= socket
.gethostname()
441 self
.socket
= socket
.socket(socket
.AF_INET6
, socket
.SOCK_STREAM
)
443 self
.socket
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
445 self
.socket
.bind((self
.localaddress
, self
.localport
))
446 self
.socket
.connect((self
.server
, self
.port
))
448 self
.ssl
= socket
.ssl(self
.socket
)
449 except socket
.error
, x
:
452 raise ServerConnectionError
, "Couldn't connect to socket: %s" % x
454 if self
.irclibobj
.fn_to_add_socket
:
455 self
.irclibobj
.fn_to_add_socket(self
.socket
)
459 self
.pass_(self
.password
)
460 self
.nick(self
.nickname
)
461 self
.user(self
.username
, self
.ircname
)
465 """Close the connection.
467 This method closes the connection permanently; after it has
468 been called, the object is unusable.
471 self
.disconnect("Closing object")
472 self
.irclibobj
._remove
_connection
(self
)
474 def _get_socket(self
):
478 def get_server_name(self
):
479 """Get the (real) server name.
481 This method returns the (real) server name, or, more
482 specifically, what the server calls itself.
485 if self
.real_server_name
:
486 return self
.real_server_name
490 def get_nickname(self
):
491 """Get the (real) nick name.
493 This method returns the (real) nickname. The library keeps
494 track of nick changes, so it might not be the nick name that
495 was passed to the connect() method. """
497 return self
.real_nickname
499 def process_data(self
):
504 new_data
= self
.ssl
.read(2**14)
506 new_data
= self
.socket
.recv(2**14)
507 except socket
.error
, x
:
508 # The server hung up.
509 self
.disconnect("Connection reset by peer")
512 # Read nothing: connection must be down.
513 self
.disconnect("Connection reset by peer")
516 lines
= _linesep_regexp
.split(self
.previous_buffer
+ new_data
)
518 # Save the last, unfinished line.
519 self
.previous_buffer
= lines
.pop()
523 print "FROM SERVER:", line
531 self
._handle
_event
(Event("all_raw_messages",
532 self
.get_server_name(),
536 m
= _rfc_1459_command_regexp
.match(line
)
537 if m
.group("prefix"):
538 prefix
= m
.group("prefix")
539 if not self
.real_server_name
:
540 self
.real_server_name
= prefix
542 if m
.group("command"):
543 command
= m
.group("command").lower()
545 if m
.group("argument"):
546 a
= m
.group("argument").split(" :", 1)
547 arguments
= a
[0].split()
549 arguments
.append(a
[1])
551 # Translate numerics into more readable strings.
552 if command
in numeric_events
:
553 command
= numeric_events
[command
]
555 if command
== "nick":
556 if nm_to_n(prefix
) == self
.real_nickname
:
557 self
.real_nickname
= arguments
[0]
558 elif command
== "welcome":
559 # Record the nickname in case the client changed nick
560 # in a nicknameinuse callback.
561 self
.real_nickname
= arguments
[0]
563 if command
in ["privmsg", "notice"]:
564 target
, message
= arguments
[0], arguments
[1]
565 messages
= _ctcp_dequote(message
)
567 if command
== "privmsg":
568 if is_channel(target
):
571 if is_channel(target
):
572 command
= "pubnotice"
574 command
= "privnotice"
577 if type(m
) is types
.TupleType
:
578 if command
in ["privmsg", "pubmsg"]:
581 command
= "ctcpreply"
585 print "command: %s, source: %s, target: %s, arguments: %s" % (
586 command
, prefix
, target
, m
)
587 self
._handle
_event
(Event(command
, prefix
, target
, m
))
588 if command
== "ctcp" and m
[0] == "ACTION":
589 self
._handle
_event
(Event("action", prefix
, target
, m
[1:]))
592 print "command: %s, source: %s, target: %s, arguments: %s" % (
593 command
, prefix
, target
, [m
])
594 self
._handle
_event
(Event(command
, prefix
, target
, [m
]))
598 if command
== "quit":
599 arguments
= [arguments
[0]]
600 elif command
== "ping":
601 target
= arguments
[0]
603 target
= arguments
[0]
604 arguments
= arguments
[1:]
606 if command
== "mode":
607 if not is_channel(target
):
611 print "command: %s, source: %s, target: %s, arguments: %s" % (
612 command
, prefix
, target
, arguments
)
613 self
._handle
_event
(Event(command
, prefix
, target
, arguments
))
615 def _handle_event(self
, event
):
617 self
.irclibobj
._handle
_event
(self
, event
)
618 if event
.eventtype() in self
.handlers
:
619 for fn
in self
.handlers
[event
.eventtype()]:
622 def is_connected(self
):
623 """Return connection status.
625 Returns true if connected, otherwise false.
627 return self
.connected
629 def add_global_handler(self
, *args
):
630 """Add global handler.
632 See documentation for IRC.add_global_handler.
634 self
.irclibobj
.add_global_handler(*args
)
636 def remove_global_handler(self
, *args
):
637 """Remove global handler.
639 See documentation for IRC.remove_global_handler.
641 self
.irclibobj
.remove_global_handler(*args
)
643 def action(self
, target
, action
):
644 """Send a CTCP ACTION command."""
645 self
.ctcp("ACTION", target
, action
)
647 def admin(self
, server
=""):
648 """Send an ADMIN command."""
649 self
.send_raw(" ".join(["ADMIN", server
]).strip())
651 def ctcp(self
, ctcptype
, target
, parameter
=""):
652 """Send a CTCP command."""
653 ctcptype
= ctcptype
.upper()
654 self
.privmsg(target
, "\001%s%s\001" % (ctcptype
, parameter
and (" " + parameter
) or ""))
656 def ctcp_reply(self
, target
, parameter
):
657 """Send a CTCP REPLY command."""
658 self
.notice(target
, "\001%s\001" % parameter
)
660 def disconnect(self
, message
=""):
661 """Hang up the connection.
665 message -- Quit message.
667 if not self
.connected
:
676 except socket
.error
, x
:
679 self
._handle
_event
(Event("disconnect", self
.server
, "", [message
]))
681 def globops(self
, text
):
682 """Send a GLOBOPS command."""
683 self
.send_raw("GLOBOPS :" + text
)
685 def info(self
, server
=""):
686 """Send an INFO command."""
687 self
.send_raw(" ".join(["INFO", server
]).strip())
689 def invite(self
, nick
, channel
):
690 """Send an INVITE command."""
691 self
.send_raw(" ".join(["INVITE", nick
, channel
]).strip())
693 def ison(self
, nicks
):
694 """Send an ISON command.
698 nicks -- List of nicks.
700 self
.send_raw("ISON " + " ".join(nicks
))
702 def join(self
, channel
, key
=""):
703 """Send a JOIN command."""
704 self
.send_raw("JOIN %s%s" % (channel
, (key
and (" " + key
))))
706 def kick(self
, channel
, nick
, comment
=""):
707 """Send a KICK command."""
708 self
.send_raw("KICK %s %s%s" % (channel
, nick
, (comment
and (" :" + comment
))))
710 def links(self
, remote_server
="", server_mask
=""):
711 """Send a LINKS command."""
714 command
= command
+ " " + remote_server
716 command
= command
+ " " + server_mask
717 self
.send_raw(command
)
719 def list(self
, channels
=None, server
=""):
720 """Send a LIST command."""
723 command
= command
+ " " + ",".join(channels
)
725 command
= command
+ " " + server
726 self
.send_raw(command
)
728 def lusers(self
, server
=""):
729 """Send a LUSERS command."""
730 self
.send_raw("LUSERS" + (server
and (" " + server
)))
732 def mode(self
, target
, command
):
733 """Send a MODE command."""
734 self
.send_raw("MODE %s %s" % (target
, command
))
736 def motd(self
, server
=""):
737 """Send an MOTD command."""
738 self
.send_raw("MOTD" + (server
and (" " + server
)))
740 def names(self
, channels
=None):
741 """Send a NAMES command."""
742 self
.send_raw("NAMES" + (channels
and (" " + ",".join(channels
)) or ""))
744 def nick(self
, newnick
):
745 """Send a NICK command."""
746 self
.send_raw("NICK " + newnick
)
748 def notice(self
, target
, text
):
749 """Send a NOTICE command."""
750 # Should limit len(text) here!
751 self
.send_raw("NOTICE %s :%s" % (target
, text
))
753 def oper(self
, nick
, password
):
754 """Send an OPER command."""
755 self
.send_raw("OPER %s %s" % (nick
, password
))
757 def part(self
, channels
, message
=""):
758 """Send a PART command."""
759 if type(channels
) == types
.StringType
:
760 self
.send_raw("PART " + channels
+ (message
and (" :" + message
)))
762 self
.send_raw("PART " + ",".join(channels
) + (message
and (" :" + message
)))
764 def pass_(self
, password
):
765 """Send a PASS command."""
766 self
.send_raw("PASS " + password
)
768 def ping(self
, target
, target2
=""):
769 """Send a PING command."""
770 self
.send_raw("PING %s%s" % (target
, target2
and (" " + target2
)))
772 def pong(self
, target
, target2
=""):
773 """Send a PONG command."""
774 self
.send_raw("PONG %s%s" % (target
, target2
and (" " + target2
)))
776 def privmsg(self
, target
, text
):
777 """Send a PRIVMSG command."""
778 # Should limit len(text) here!
779 self
.send_raw("PRIVMSG %s :%s" % (target
, text
))
781 def privmsg_many(self
, targets
, text
):
782 """Send a PRIVMSG command to multiple targets."""
783 # Should limit len(text) here!
784 self
.send_raw("PRIVMSG %s :%s" % (",".join(targets
), text
))
786 def quit(self
, message
=""):
787 """Send a QUIT command."""
788 # Note that many IRC servers don't use your QUIT message
789 # unless you've been connected for at least 5 minutes!
790 self
.send_raw("QUIT" + (message
and (" :" + message
)))
792 def send_raw(self
, string
):
793 """Send raw string to the server.
795 The string will be padded with appropriate CR LF.
797 if self
.socket
is None:
798 raise ServerNotConnectedError
, "Not connected."
801 self
.ssl
.write(string
+ "\r\n")
803 self
.socket
.send(string
+ "\r\n")
805 print "TO SERVER:", string
806 except socket
.error
, x
:
808 self
.disconnect("Connection reset by peer.")
810 def squit(self
, server
, comment
=""):
811 """Send an SQUIT command."""
812 self
.send_raw("SQUIT %s%s" % (server
, comment
and (" :" + comment
)))
814 def stats(self
, statstype
, server
=""):
815 """Send a STATS command."""
816 self
.send_raw("STATS %s%s" % (statstype
, server
and (" " + server
)))
818 def time(self
, server
=""):
819 """Send a TIME command."""
820 self
.send_raw("TIME" + (server
and (" " + server
)))
822 def topic(self
, channel
, new_topic
=None):
823 """Send a TOPIC command."""
824 if new_topic
is None:
825 self
.send_raw("TOPIC " + channel
)
827 self
.send_raw("TOPIC %s :%s" % (channel
, new_topic
))
829 def trace(self
, target
=""):
830 """Send a TRACE command."""
831 self
.send_raw("TRACE" + (target
and (" " + target
)))
833 def user(self
, username
, realname
):
834 """Send a USER command."""
835 self
.send_raw("USER %s 0 * :%s" % (username
, realname
))
837 def userhost(self
, nicks
):
838 """Send a USERHOST command."""
839 self
.send_raw("USERHOST " + ",".join(nicks
))
841 def users(self
, server
=""):
842 """Send a USERS command."""
843 self
.send_raw("USERS" + (server
and (" " + server
)))
845 def version(self
, server
=""):
846 """Send a VERSION command."""
847 self
.send_raw("VERSION" + (server
and (" " + server
)))
849 def wallops(self
, text
):
850 """Send a WALLOPS command."""
851 self
.send_raw("WALLOPS :" + text
)
853 def who(self
, target
="", op
=""):
854 """Send a WHO command."""
855 self
.send_raw("WHO%s%s" % (target
and (" " + target
), op
and (" o")))
857 def whois(self
, targets
):
858 """Send a WHOIS command."""
859 self
.send_raw("WHOIS " + ",".join(targets
))
861 def whowas(self
, nick
, max="", server
=""):
862 """Send a WHOWAS command."""
863 self
.send_raw("WHOWAS %s%s%s" % (nick
,
865 server
and (" " + server
)))
867 class DCCConnectionError(IRCError
):
871 class DCCConnection(Connection
):
872 """This class represents a DCC connection.
874 DCCConnection objects are instantiated by calling the dcc
875 method on an IRC object.
877 def __init__(self
, irclibobj
, dcctype
):
878 Connection
.__init
__(self
, irclibobj
)
881 self
.dcctype
= dcctype
882 self
.peeraddress
= None
885 def connect(self
, address
, port
):
886 """Connect/reconnect to a DCC peer.
889 address -- Host/IP address of the peer.
891 port -- The port number to connect to.
893 Returns the DCCConnection object.
895 self
.peeraddress
= socket
.gethostbyname(address
)
898 self
.previous_buffer
= ""
900 self
.socket
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
903 self
.socket
.connect((self
.peeraddress
, self
.peerport
))
904 except socket
.error
, x
:
905 raise DCCConnectionError
, "Couldn't connect to socket: %s" % x
907 if self
.irclibobj
.fn_to_add_socket
:
908 self
.irclibobj
.fn_to_add_socket(self
.socket
)
912 """Wait for a connection/reconnection from a DCC peer.
914 Returns the DCCConnection object.
916 The local IP address and port are available as
917 self.localaddress and self.localport. After connection from a
918 peer, the peer address and port are available as
919 self.peeraddress and self.peerport.
921 self
.previous_buffer
= ""
923 self
.socket
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
926 self
.socket
.bind((socket
.gethostbyname(socket
.gethostname()), 0))
927 self
.localaddress
, self
.localport
= self
.socket
.getsockname()
928 self
.socket
.listen(10)
929 except socket
.error
, x
:
930 raise DCCConnectionError
, "Couldn't bind socket: %s" % x
933 def disconnect(self
, message
=""):
934 """Hang up the connection and close the object.
938 message -- Quit message.
940 if not self
.connected
:
946 except socket
.error
, x
:
949 self
.irclibobj
._handle
_event
(
951 Event("dcc_disconnect", self
.peeraddress
, "", [message
]))
952 self
.irclibobj
._remove
_connection
(self
)
954 def process_data(self
):
957 if self
.passive
and not self
.connected
:
958 conn
, (self
.peeraddress
, self
.peerport
) = self
.socket
.accept()
963 print "DCC connection from %s:%d" % (
964 self
.peeraddress
, self
.peerport
)
965 self
.irclibobj
._handle
_event
(
967 Event("dcc_connect", self
.peeraddress
, None, None))
971 new_data
= self
.socket
.recv(2**14)
972 except socket
.error
, x
:
973 # The server hung up.
974 self
.disconnect("Connection reset by peer")
977 # Read nothing: connection must be down.
978 self
.disconnect("Connection reset by peer")
981 if self
.dcctype
== "chat":
982 # The specification says lines are terminated with LF, but
983 # it seems safer to handle CR LF terminations too.
984 chunks
= _linesep_regexp
.split(self
.previous_buffer
+ new_data
)
986 # Save the last, unfinished line.
987 self
.previous_buffer
= chunks
[-1]
988 if len(self
.previous_buffer
) > 2**14:
989 # Bad peer! Naughty peer!
997 prefix
= self
.peeraddress
1001 print "FROM PEER:", chunk
1004 print "command: %s, source: %s, target: %s, arguments: %s" % (
1005 command
, prefix
, target
, arguments
)
1006 self
.irclibobj
._handle
_event
(
1008 Event(command
, prefix
, target
, arguments
))
1010 def _get_socket(self
):
1014 def privmsg(self
, string
):
1015 """Send data to DCC peer.
1017 The string will be padded with appropriate LF if it's a DCC
1021 self
.socket
.send(string
)
1022 if self
.dcctype
== "chat":
1023 self
.socket
.send("\n")
1025 print "TO PEER: %s\n" % string
1026 except socket
.error
, x
:
1028 self
.disconnect("Connection reset by peer.")
1030 class SimpleIRCClient
:
1031 """A simple single-server IRC client class.
1033 This is an example of an object-oriented wrapper of the IRC
1034 framework. A real IRC client can be made by subclassing this
1035 class and adding appropriate methods.
1037 The method on_join will be called when a "join" event is created
1038 (which is done when the server sends a JOIN messsage/command),
1039 on_privmsg will be called for "privmsg" events, and so on. The
1040 handler methods get two arguments: the connection object (same as
1041 self.connection) and the event object.
1043 Instance attributes that can be used by sub classes:
1045 ircobj -- The IRC instance.
1047 connection -- The ServerConnection instance.
1049 dcc_connections -- A list of DCCConnection instances.
1053 self
.connection
= self
.ircobj
.server()
1054 self
.dcc_connections
= []
1055 self
.ircobj
.add_global_handler("all_events", self
._dispatcher
, -10)
1056 self
.ircobj
.add_global_handler("dcc_disconnect", self
._dcc
_disconnect
, -10)
1058 def _dispatcher(self
, c
, e
):
1060 m
= "on_" + e
.eventtype()
1061 if hasattr(self
, m
):
1062 getattr(self
, m
)(c
, e
)
1064 def _dcc_disconnect(self
, c
, e
):
1065 self
.dcc_connections
.remove(c
)
1067 def connect(self
, server
, port
, nickname
, password
=None, username
=None,
1068 ircname
=None, localaddress
="", localport
=0, ssl
=False, ipv6
=False):
1069 """Connect/reconnect to a server.
1073 server -- Server name.
1075 port -- Port number.
1077 nickname -- The nickname.
1079 password -- Password (if any).
1081 username -- The username.
1083 ircname -- The IRC name.
1085 localaddress -- Bind the connection to a specific local IP address.
1087 localport -- Bind the connection to a specific local port.
1089 ssl -- Enable support for ssl.
1091 ipv6 -- Enable support for ipv6.
1093 This function can be called to reconnect a closed connection.
1095 self
.connection
.connect(server
, port
, nickname
,
1096 password
, username
, ircname
,
1097 localaddress
, localport
, ssl
, ipv6
)
1099 def dcc_connect(self
, address
, port
, dcctype
="chat"):
1100 """Connect to a DCC peer.
1104 address -- IP address of the peer.
1106 port -- Port to connect to.
1108 Returns a DCCConnection instance.
1110 dcc
= self
.ircobj
.dcc(dcctype
)
1111 self
.dcc_connections
.append(dcc
)
1112 dcc
.connect(address
, port
)
1115 def dcc_listen(self
, dcctype
="chat"):
1116 """Listen for connections from a DCC peer.
1118 Returns a DCCConnection instance.
1120 dcc
= self
.ircobj
.dcc(dcctype
)
1121 self
.dcc_connections
.append(dcc
)
1126 """Start the IRC client."""
1127 self
.ircobj
.process_forever()
1131 """Class representing an IRC event."""
1132 def __init__(self
, eventtype
, source
, target
, arguments
=None):
1133 """Constructor of Event objects.
1137 eventtype -- A string describing the event.
1139 source -- The originator of the event (a nick mask or a server).
1141 target -- The target of the event (a nick or a channel).
1143 arguments -- Any event specific arguments.
1145 self
._eventtype
= eventtype
1146 self
._source
= source
1147 self
._target
= target
1149 self
._arguments
= arguments
1151 self
._arguments
= []
1153 def eventtype(self
):
1154 """Get the event type."""
1155 return self
._eventtype
1158 """Get the event source."""
1162 """Get the event target."""
1165 def arguments(self
):
1166 """Get the event arguments."""
1167 return self
._arguments
1169 _LOW_LEVEL_QUOTE
= "\020"
1170 _CTCP_LEVEL_QUOTE
= "\134"
1171 _CTCP_DELIMITER
= "\001"
1173 _low_level_mapping
= {
1177 _LOW_LEVEL_QUOTE
: _LOW_LEVEL_QUOTE
1180 _low_level_regexp
= re
.compile(_LOW_LEVEL_QUOTE
+ "(.)")
1182 def mask_matches(nick
, mask
):
1183 """Check if a nick matches a mask.
1185 Returns true if the nick matches, otherwise false.
1187 nick
= irc_lower(nick
)
1188 mask
= irc_lower(mask
)
1189 mask
= mask
.replace("\\", "\\\\")
1190 for ch
in ".$|[](){}+":
1191 mask
= mask
.replace(ch
, "\\" + ch
)
1192 mask
= mask
.replace("?", ".")
1193 mask
= mask
.replace("*", ".*")
1194 r
= re
.compile(mask
, re
.IGNORECASE
)
1195 return r
.match(nick
)
1197 _special
= "-[]\\`^{}"
1198 nick_characters
= string
.ascii_letters
+ string
.digits
+ _special
1199 _ircstring_translation
= string
.maketrans(string
.ascii_uppercase
+ "[]\\^",
1200 string
.ascii_lowercase
+ "{}|~")
1203 """Returns a lowercased string.
1205 The definition of lowercased comes from the IRC specification (RFC
1208 return s
.translate(_ircstring_translation
)
1210 def _ctcp_dequote(message
):
1211 """[Internal] Dequote a message according to CTCP specifications.
1213 The function returns a list where each element can be either a
1214 string (normal message) or a tuple of one or two strings (tagged
1215 messages). If a tuple has only one element (ie is a singleton),
1216 that element is the tag; otherwise the tuple has two elements: the
1221 message -- The message to be decoded.
1224 def _low_level_replace(match_obj
):
1225 ch
= match_obj
.group(1)
1227 # If low_level_mapping doesn't have the character as key, we
1228 # should just return the character.
1229 return _low_level_mapping
.get(ch
, ch
)
1231 if _LOW_LEVEL_QUOTE
in message
:
1232 # Yup, there was a quote. Release the dequoter, man!
1233 message
= _low_level_regexp
.sub(_low_level_replace
, message
)
1235 if _CTCP_DELIMITER
not in message
:
1238 # Split it into parts. (Does any IRC client actually *use*
1239 # CTCP stacking like this?)
1240 chunks
= message
.split(_CTCP_DELIMITER
)
1244 while i
< len(chunks
)-1:
1245 # Add message if it's non-empty.
1246 if len(chunks
[i
]) > 0:
1247 messages
.append(chunks
[i
])
1249 if i
< len(chunks
)-2:
1250 # Aye! CTCP tagged data ahead!
1251 messages
.append(tuple(chunks
[i
+1].split(" ", 1)))
1255 if len(chunks
) % 2 == 0:
1256 # Hey, a lonely _CTCP_DELIMITER at the end! This means
1257 # that the last chunk, including the delimiter, is a
1258 # normal message! (This is according to the CTCP
1260 messages
.append(_CTCP_DELIMITER
+ chunks
[-1])
1264 def is_channel(string
):
1265 """Check if a string is a channel name.
1267 Returns true if the argument is a channel name, otherwise false.
1269 return string
and string
[0] in "#&+!"
1271 def ip_numstr_to_quad(num
):
1272 """Convert an IP number as an integer given in ASCII
1273 representation (e.g. '3232235521') to an IP address string
1274 (e.g. '192.168.0.1')."""
1276 p
= map(str, map(int, [n
>> 24 & 0xFF, n
>> 16 & 0xFF,
1277 n
>> 8 & 0xFF, n
& 0xFF]))
1280 def ip_quad_to_numstr(quad
):
1281 """Convert an IP address string (e.g. '192.168.0.1') to an IP
1282 number as an integer given in ASCII representation
1283 (e.g. '3232235521')."""
1284 p
= map(long, quad
.split("."))
1285 s
= str((p
[0] << 24) |
(p
[1] << 16) |
(p
[2] << 8) | p
[3])
1291 """Get the nick part of a nickmask.
1293 (The source of an Event is a nickmask.)
1295 return s
.split("!")[0]
1298 """Get the userhost part of a nickmask.
1300 (The source of an Event is a nickmask.)
1302 return s
.split("!")[1]
1305 """Get the host part of a nickmask.
1307 (The source of an Event is a nickmask.)
1309 return s
.split("@")[1]
1312 """Get the user part of a nickmask.
1314 (The source of an Event is a nickmask.)
1317 return s
.split("@")[0]
1319 def parse_nick_modes(mode_string
):
1320 """Parse a nick mode string.
1322 The function returns a list of lists with three members: sign,
1323 mode and argument. The sign is \"+\" or \"-\". The argument is
1328 >>> irclib.parse_nick_modes(\"+ab-c\")
1329 [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1332 return _parse_modes(mode_string
, "")
1334 def parse_channel_modes(mode_string
):
1335 """Parse a channel mode string.
1337 The function returns a list of lists with three members: sign,
1338 mode and argument. The sign is \"+\" or \"-\". The argument is
1339 None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
1343 >>> irclib.parse_channel_modes(\"+ab-c foo\")
1344 [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1347 return _parse_modes(mode_string
, "bklvo")
1349 def _parse_modes(mode_string
, unary_modes
=""):
1357 a
= mode_string
.split()
1361 mode_part
, args
= a
[0], a
[1:]
1363 if mode_part
[0] not in "+-":
1365 for ch
in mode_part
:
1369 collecting_arguments
= 1
1370 elif ch
in unary_modes
:
1371 if len(args
) >= arg_count
+ 1:
1372 modes
.append([sign
, ch
, args
[arg_count
]])
1373 arg_count
= arg_count
+ 1
1375 modes
.append([sign
, ch
, None])
1377 modes
.append([sign
, ch
, None])
1380 def _ping_ponger(connection
, event
):
1382 connection
.pong(event
.target())
1384 # Numeric table mostly stolen from the Perl IRC module (Net::IRC).
1390 "005": "featurelist", # XXX
1392 "201": "traceconnecting",
1393 "202": "tracehandshake",
1394 "203": "traceunknown",
1395 "204": "traceoperator",
1397 "206": "traceserver",
1398 "207": "traceservice",
1399 "208": "tracenewtype",
1400 "209": "traceclass",
1401 "210": "tracereconnect",
1402 "211": "statslinkinfo",
1403 "212": "statscommands",
1404 "213": "statscline",
1405 "214": "statsnline",
1406 "215": "statsiline",
1407 "216": "statskline",
1408 "217": "statsqline",
1409 "218": "statsyline",
1410 "219": "endofstats",
1412 "231": "serviceinfo",
1413 "232": "endofservices",
1416 "235": "servlistend",
1417 "241": "statslline",
1418 "242": "statsuptime",
1419 "243": "statsoline",
1420 "244": "statshline",
1421 "250": "luserconns",
1422 "251": "luserclient",
1424 "253": "luserunknown",
1425 "254": "luserchannels",
1430 "259": "adminemail",
1432 "262": "endoftrace",
1443 "312": "whoisserver",
1444 "313": "whoisoperator",
1445 "314": "whowasuser",
1447 "316": "whoischanop",
1449 "318": "endofwhois",
1450 "319": "whoischannels",
1454 "324": "channelmodeis",
1455 "329": "channelcreate",
1457 "332": "currenttopic",
1461 "346": "invitelist",
1462 "347": "endofinvitelist",
1463 "348": "exceptlist",
1464 "349": "endofexceptlist",
1472 "365": "endoflinks",
1473 "366": "endofnames",
1475 "368": "endofbanlist",
1476 "369": "endofwhowas",
1483 "377": "motd2", # 1997-10-16 -- tkil
1488 "392": "usersstart",
1490 "394": "endofusers",
1492 "401": "nosuchnick",
1493 "402": "nosuchserver",
1494 "403": "nosuchchannel",
1495 "404": "cannotsendtochan",
1496 "405": "toomanychannels",
1497 "406": "wasnosuchnick",
1498 "407": "toomanytargets",
1500 "411": "norecipient",
1501 "412": "notexttosend",
1502 "413": "notoplevel",
1503 "414": "wildtoplevel",
1504 "421": "unknowncommand",
1506 "423": "noadmininfo",
1508 "431": "nonicknamegiven",
1509 "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
1510 "433": "nicknameinuse",
1511 "436": "nickcollision",
1512 "437": "unavailresource", # "Nick temporally unavailable"
1513 "441": "usernotinchannel",
1514 "442": "notonchannel",
1515 "443": "useronchannel",
1517 "445": "summondisabled",
1518 "446": "usersdisabled",
1519 "451": "notregistered",
1520 "461": "needmoreparams",
1521 "462": "alreadyregistered",
1522 "463": "nopermforhost",
1523 "464": "passwdmismatch",
1524 "465": "yourebannedcreep", # I love this one...
1525 "466": "youwillbebanned",
1527 "471": "channelisfull",
1528 "472": "unknownmode",
1529 "473": "inviteonlychan",
1530 "474": "bannedfromchan",
1531 "475": "badchannelkey",
1532 "476": "badchanmask",
1533 "477": "nochanmodes", # "Channel doesn't support modes"
1534 "478": "banlistfull",
1535 "481": "noprivileges",
1536 "482": "chanoprivsneeded",
1537 "483": "cantkillserver",
1538 "484": "restricted", # Connection is restricted
1539 "485": "uniqopprivsneeded",
1540 "491": "nooperhost",
1541 "492": "noservicehost",
1542 "501": "umodeunknownflag",
1543 "502": "usersdontmatch",
1546 generated_events
= [
1557 # IRC protocol events
1573 all_events
= generated_events
+ protocol_events
+ numeric_events
.values()