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

Side by Side Diff: third_party/twisted_8_1/twisted/words/protocols/irc.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.words.test.test_irc -*-
2 # Copyright (c) 2001-2005 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """Internet Relay Chat Protocol for client and server.
7
8 Future Plans
9 ============
10
11 The way the IRCClient class works here encourages people to implement
12 IRC clients by subclassing the ephemeral protocol class, and it tends
13 to end up with way more state than it should for an object which will
14 be destroyed as soon as the TCP transport drops. Someone oughta do
15 something about that, ya know?
16
17 The DCC support needs to have more hooks for the client for it to be
18 able to ask the user things like \"Do you want to accept this session?\"
19 and \"Transfer #2 is 67% done.\" and otherwise manage the DCC sessions.
20
21 Test coverage needs to be better.
22
23 @author: U{Kevin Turner<mailto:acapnotic@twistedmatrix.com>}
24
25 @see: RFC 1459: Internet Relay Chat Protocol
26 @see: RFC 2812: Internet Relay Chat: Client Protocol
27 @see: U{The Client-To-Client-Protocol
28 <http://www.irchelp.org/irchelp/rfc/ctcpspec.html>}
29 """
30
31 __version__ = '$Revision: 1.94 $'[11:-2]
32
33 from twisted.internet import reactor, protocol
34 from twisted.persisted import styles
35 from twisted.protocols import basic
36 from twisted.python import log, reflect, text
37
38 # System Imports
39
40 import errno
41 import os
42 import random
43 import re
44 import stat
45 import string
46 import struct
47 import sys
48 import time
49 import types
50 import traceback
51 import socket
52
53 from os import path
54
55 NUL = chr(0)
56 CR = chr(015)
57 NL = chr(012)
58 LF = NL
59 SPC = chr(040)
60
61 CHANNEL_PREFIXES = '&#!+'
62
63 class IRCBadMessage(Exception):
64 pass
65
66 class IRCPasswordMismatch(Exception):
67 pass
68
69 def parsemsg(s):
70 """Breaks a message from an IRC server into its prefix, command, and argumen ts.
71 """
72 prefix = ''
73 trailing = []
74 if not s:
75 raise IRCBadMessage("Empty line.")
76 if s[0] == ':':
77 prefix, s = s[1:].split(' ', 1)
78 if s.find(' :') != -1:
79 s, trailing = s.split(' :', 1)
80 args = s.split()
81 args.append(trailing)
82 else:
83 args = s.split()
84 command = args.pop(0)
85 return prefix, command, args
86
87
88 def split(str, length = 80):
89 """I break a message into multiple lines.
90
91 I prefer to break at whitespace near str[length]. I also break at \\n.
92
93 @returns: list of strings
94 """
95 if length <= 0:
96 raise ValueError("Length must be a number greater than zero")
97 r = []
98 while len(str) > length:
99 w, n = str[:length].rfind(' '), str[:length].find('\n')
100 if w == -1 and n == -1:
101 line, str = str[:length], str[length:]
102 else:
103 i = n == -1 and w or n
104 line, str = str[:i], str[i+1:]
105 r.append(line)
106 if len(str):
107 r.extend(str.split('\n'))
108 return r
109
110 class IRC(protocol.Protocol):
111 """Internet Relay Chat server protocol.
112 """
113
114 buffer = ""
115 hostname = None
116
117 encoding = None
118
119 def connectionMade(self):
120 self.channels = []
121 if self.hostname is None:
122 self.hostname = socket.getfqdn()
123
124
125 def sendLine(self, line):
126 if self.encoding is not None:
127 if isinstance(line, unicode):
128 line = line.encode(self.encoding)
129 self.transport.write("%s%s%s" % (line, CR, LF))
130
131
132 def sendMessage(self, command, *parameter_list, **prefix):
133 """Send a line formatted as an IRC message.
134
135 First argument is the command, all subsequent arguments
136 are parameters to that command. If a prefix is desired,
137 it may be specified with the keyword argument 'prefix'.
138 """
139
140 if not command:
141 raise ValueError, "IRC message requires a command."
142
143 if ' ' in command or command[0] == ':':
144 # Not the ONLY way to screw up, but provides a little
145 # sanity checking to catch likely dumb mistakes.
146 raise ValueError, "Somebody screwed up, 'cuz this doesn't" \
147 " look like a command to me: %s" % command
148
149 line = string.join([command] + list(parameter_list))
150 if prefix.has_key('prefix'):
151 line = ":%s %s" % (prefix['prefix'], line)
152 self.sendLine(line)
153
154 if len(parameter_list) > 15:
155 log.msg("Message has %d parameters (RFC allows 15):\n%s" %
156 (len(parameter_list), line))
157
158
159 def dataReceived(self, data):
160 """This hack is to support mIRC, which sends LF only,
161 even though the RFC says CRLF. (Also, the flexibility
162 of LineReceiver to turn "line mode" on and off was not
163 required.)
164 """
165 lines = (self.buffer + data).split(LF)
166 # Put the (possibly empty) element after the last LF back in the
167 # buffer
168 self.buffer = lines.pop()
169
170 for line in lines:
171 if len(line) <= 2:
172 # This is a blank line, at best.
173 continue
174 if line[-1] == CR:
175 line = line[:-1]
176 prefix, command, params = parsemsg(line)
177 # mIRC is a big pile of doo-doo
178 command = command.upper()
179 # DEBUG: log.msg( "%s %s %s" % (prefix, command, params))
180
181 self.handleCommand(command, prefix, params)
182
183
184 def handleCommand(self, command, prefix, params):
185 """Determine the function to call for the given command and call
186 it with the given arguments.
187 """
188 method = getattr(self, "irc_%s" % command, None)
189 try:
190 if method is not None:
191 method(prefix, params)
192 else:
193 self.irc_unknown(prefix, command, params)
194 except:
195 log.deferr()
196
197
198 def irc_unknown(self, prefix, command, params):
199 """Implement me!"""
200 raise NotImplementedError(command, prefix, params)
201
202
203 # Helper methods
204 def privmsg(self, sender, recip, message):
205 """Send a message to a channel or user
206
207 @type sender: C{str} or C{unicode}
208 @param sender: Who is sending this message. Should be of the form
209 username!ident@hostmask (unless you know better!).
210
211 @type recip: C{str} or C{unicode}
212 @param recip: The recipient of this message. If a channel, it
213 must start with a channel prefix.
214
215 @type message: C{str} or C{unicode}
216 @param message: The message being sent.
217 """
218 self.sendLine(":%s PRIVMSG %s :%s" % (sender, recip, lowQuote(message)))
219
220
221 def notice(self, sender, recip, message):
222 """Send a \"notice\" to a channel or user.
223
224 Notices differ from privmsgs in that the RFC claims they are different.
225 Robots are supposed to send notices and not respond to them. Clients
226 typically display notices differently from privmsgs.
227
228 @type sender: C{str} or C{unicode}
229 @param sender: Who is sending this message. Should be of the form
230 username!ident@hostmask (unless you know better!).
231
232 @type recip: C{str} or C{unicode}
233 @param recip: The recipient of this message. If a channel, it
234 must start with a channel prefix.
235
236 @type message: C{str} or C{unicode}
237 @param message: The message being sent.
238 """
239 self.sendLine(":%s NOTICE %s :%s" % (sender, recip, message))
240
241
242 def action(self, sender, recip, message):
243 """Send an action to a channel or user.
244
245 @type sender: C{str} or C{unicode}
246 @param sender: Who is sending this message. Should be of the form
247 username!ident@hostmask (unless you know better!).
248
249 @type recip: C{str} or C{unicode}
250 @param recip: The recipient of this message. If a channel, it
251 must start with a channel prefix.
252
253 @type message: C{str} or C{unicode}
254 @param message: The action being sent.
255 """
256 self.sendLine(":%s ACTION %s :%s" % (sender, recip, message))
257
258
259 def topic(self, user, channel, topic, author=None):
260 """Send the topic to a user.
261
262 @type user: C{str} or C{unicode}
263 @param user: The user receiving the topic. Only their nick name, not
264 the full hostmask.
265
266 @type channel: C{str} or C{unicode}
267 @param channel: The channel for which this is the topic.
268
269 @type topic: C{str} or C{unicode} or C{None}
270 @param topic: The topic string, unquoted, or None if there is
271 no topic.
272
273 @type author: C{str} or C{unicode}
274 @param author: If the topic is being changed, the full username and host mask
275 of the person changing it.
276 """
277 if author is None:
278 if topic is None:
279 self.sendLine(':%s %s %s %s :%s' % (
280 self.hostname, RPL_NOTOPIC, user, channel, 'No topic is set. '))
281 else:
282 self.sendLine(":%s %s %s %s :%s" % (
283 self.hostname, RPL_TOPIC, user, channel, lowQuote(topic)))
284 else:
285 self.sendLine(":%s TOPIC %s :%s" % (author, channel, lowQuote(topic) ))
286
287
288 def topicAuthor(self, user, channel, author, date):
289 """
290 Send the author of and time at which a topic was set for the given
291 channel.
292
293 This sends a 333 reply message, which is not part of the IRC RFC.
294
295 @type user: C{str} or C{unicode}
296 @param user: The user receiving the topic. Only their nick name, not
297 the full hostmask.
298
299 @type channel: C{str} or C{unicode}
300 @param channel: The channel for which this information is relevant.
301
302 @type author: C{str} or C{unicode}
303 @param author: The nickname (without hostmask) of the user who last
304 set the topic.
305
306 @type date: C{int}
307 @param date: A POSIX timestamp (number of seconds since the epoch)
308 at which the topic was last set.
309 """
310 self.sendLine(':%s %d %s %s %s %d' % (
311 self.hostname, 333, user, channel, author, date))
312
313
314 def names(self, user, channel, names):
315 """Send the names of a channel's participants to a user.
316
317 @type user: C{str} or C{unicode}
318 @param user: The user receiving the name list. Only their nick
319 name, not the full hostmask.
320
321 @type channel: C{str} or C{unicode}
322 @param channel: The channel for which this is the namelist.
323
324 @type names: C{list} of C{str} or C{unicode}
325 @param names: The names to send.
326 """
327 # XXX If unicode is given, these limits are not quite correct
328 prefixLength = len(channel) + len(user) + 10
329 namesLength = 512 - prefixLength
330
331 L = []
332 count = 0
333 for n in names:
334 if count + len(n) + 1 > namesLength:
335 self.sendLine(":%s %s %s = %s :%s" % (
336 self.hostname, RPL_NAMREPLY, user, channel, ' '.join(L)))
337 L = [n]
338 count = len(n)
339 else:
340 L.append(n)
341 count += len(n) + 1
342 if L:
343 self.sendLine(":%s %s %s = %s :%s" % (
344 self.hostname, RPL_NAMREPLY, user, channel, ' '.join(L)))
345 self.sendLine(":%s %s %s %s :End of /NAMES list" % (
346 self.hostname, RPL_ENDOFNAMES, user, channel))
347
348
349 def who(self, user, channel, memberInfo):
350 """
351 Send a list of users participating in a channel.
352
353 @type user: C{str} or C{unicode}
354 @param user: The user receiving this member information. Only their
355 nick name, not the full hostmask.
356
357 @type channel: C{str} or C{unicode}
358 @param channel: The channel for which this is the member
359 information.
360
361 @type memberInfo: C{list} of C{tuples}
362 @param memberInfo: For each member of the given channel, a 7-tuple
363 containing their username, their hostmask, the server to which they
364 are connected, their nickname, the letter "H" or "G" (wtf do these
365 mean?), the hopcount from C{user} to this member, and this member's
366 real name.
367 """
368 for info in memberInfo:
369 (username, hostmask, server, nickname, flag, hops, realName) = info
370 assert flag in ("H", "G")
371 self.sendLine(":%s %s %s %s %s %s %s %s %s :%d %s" % (
372 self.hostname, RPL_WHOREPLY, user, channel,
373 username, hostmask, server, nickname, flag, hops, realName))
374
375 self.sendLine(":%s %s %s %s :End of /WHO list." % (
376 self.hostname, RPL_ENDOFWHO, user, channel))
377
378
379 def whois(self, user, nick, username, hostname, realName, server, serverInfo , oper, idle, signOn, channels):
380 """
381 Send information about the state of a particular user.
382
383 @type user: C{str} or C{unicode}
384 @param user: The user receiving this information. Only their nick
385 name, not the full hostmask.
386
387 @type nick: C{str} or C{unicode}
388 @param nick: The nickname of the user this information describes.
389
390 @type username: C{str} or C{unicode}
391 @param username: The user's username (eg, ident response)
392
393 @type hostname: C{str}
394 @param hostname: The user's hostmask
395
396 @type realName: C{str} or C{unicode}
397 @param realName: The user's real name
398
399 @type server: C{str} or C{unicode}
400 @param server: The name of the server to which the user is connected
401
402 @type serverInfo: C{str} or C{unicode}
403 @param serverInfo: A descriptive string about that server
404
405 @type oper: C{bool}
406 @param oper: Indicates whether the user is an IRC operator
407
408 @type idle: C{int}
409 @param idle: The number of seconds since the user last sent a message
410
411 @type signOn: C{int}
412 @param signOn: A POSIX timestamp (number of seconds since the epoch)
413 indicating the time the user signed on
414
415 @type channels: C{list} of C{str} or C{unicode}
416 @param channels: A list of the channels which the user is participating in
417 """
418 self.sendLine(":%s %s %s %s %s %s * :%s" % (
419 self.hostname, RPL_WHOISUSER, user, nick, username, hostname, realNa me))
420 self.sendLine(":%s %s %s %s %s :%s" % (
421 self.hostname, RPL_WHOISSERVER, user, nick, server, serverInfo))
422 if oper:
423 self.sendLine(":%s %s %s %s :is an IRC operator" % (
424 self.hostname, RPL_WHOISOPERATOR, user, nick))
425 self.sendLine(":%s %s %s %s %d %d :seconds idle, signon time" % (
426 self.hostname, RPL_WHOISIDLE, user, nick, idle, signOn))
427 self.sendLine(":%s %s %s %s :%s" % (
428 self.hostname, RPL_WHOISCHANNELS, user, nick, ' '.join(channels)))
429 self.sendLine(":%s %s %s %s :End of WHOIS list." % (
430 self.hostname, RPL_ENDOFWHOIS, user, nick))
431
432
433 def join(self, who, where):
434 """Send a join message.
435
436 @type who: C{str} or C{unicode}
437 @param who: The name of the user joining. Should be of the form
438 username!ident@hostmask (unless you know better!).
439
440 @type where: C{str} or C{unicode}
441 @param where: The channel the user is joining.
442 """
443 self.sendLine(":%s JOIN %s" % (who, where))
444
445
446 def part(self, who, where, reason=None):
447 """Send a part message.
448
449 @type who: C{str} or C{unicode}
450 @param who: The name of the user joining. Should be of the form
451 username!ident@hostmask (unless you know better!).
452
453 @type where: C{str} or C{unicode}
454 @param where: The channel the user is joining.
455
456 @type reason: C{str} or C{unicode}
457 @param reason: A string describing the misery which caused
458 this poor soul to depart.
459 """
460 if reason:
461 self.sendLine(":%s PART %s :%s" % (who, where, reason))
462 else:
463 self.sendLine(":%s PART %s" % (who, where))
464
465
466 def channelMode(self, user, channel, mode, *args):
467 """
468 Send information about the mode of a channel.
469
470 @type user: C{str} or C{unicode}
471 @param user: The user receiving the name list. Only their nick
472 name, not the full hostmask.
473
474 @type channel: C{str} or C{unicode}
475 @param channel: The channel for which this is the namelist.
476
477 @type mode: C{str}
478 @param mode: A string describing this channel's modes.
479
480 @param args: Any additional arguments required by the modes.
481 """
482 self.sendLine(":%s %s %s %s %s %s" % (
483 self.hostname, RPL_CHANNELMODEIS, user, channel, mode, ' '.join(args )))
484
485
486 class IRCClient(basic.LineReceiver):
487 """Internet Relay Chat client protocol, with sprinkles.
488
489 In addition to providing an interface for an IRC client protocol,
490 this class also contains reasonable implementations of many common
491 CTCP methods.
492
493 TODO
494 ====
495 - Limit the length of messages sent (because the IRC server probably
496 does).
497 - Add flood protection/rate limiting for my CTCP replies.
498 - NickServ cooperation. (a mix-in?)
499 - Heartbeat. The transport may die in such a way that it does not realize
500 it is dead until it is written to. Sending something (like \"PING
501 this.irc-host.net\") during idle peroids would alleviate that. If
502 you're concerned with the stability of the host as well as that of the
503 transport, you might care to watch for the corresponding PONG.
504
505 @ivar nickname: Nickname the client will use.
506 @ivar password: Password used to log on to the server. May be C{None}.
507 @ivar realname: Supplied to the server during login as the \"Real name\"
508 or \"ircname\". May be C{None}.
509 @ivar username: Supplied to the server during login as the \"User name\".
510 May be C{None}
511
512 @ivar userinfo: Sent in reply to a X{USERINFO} CTCP query. If C{None}, no
513 USERINFO reply will be sent.
514 \"This is used to transmit a string which is settable by
515 the user (and never should be set by the client).\"
516 @ivar fingerReply: Sent in reply to a X{FINGER} CTCP query. If C{None}, no
517 FINGER reply will be sent.
518 @type fingerReply: Callable or String
519
520 @ivar versionName: CTCP VERSION reply, client name. If C{None}, no VERSION
521 reply will be sent.
522 @ivar versionNum: CTCP VERSION reply, client version,
523 @ivar versionEnv: CTCP VERSION reply, environment the client is running in.
524
525 @ivar sourceURL: CTCP SOURCE reply, a URL where the source code of this
526 client may be found. If C{None}, no SOURCE reply will be sent.
527
528 @ivar lineRate: Minimum delay between lines sent to the server. If
529 C{None}, no delay will be imposed.
530 @type lineRate: Number of Seconds.
531 """
532
533 motd = ""
534 nickname = 'irc'
535 password = None
536 realname = None
537 username = None
538 ### Responses to various CTCP queries.
539
540 userinfo = None
541 # fingerReply is a callable returning a string, or a str()able object.
542 fingerReply = None
543 versionName = None
544 versionNum = None
545 versionEnv = None
546
547 sourceURL = "http://twistedmatrix.com/downloads/"
548
549 dcc_destdir = '.'
550 dcc_sessions = None
551
552 # If this is false, no attempt will be made to identify
553 # ourself to the server.
554 performLogin = 1
555
556 lineRate = None
557 _queue = None
558 _queueEmptying = None
559
560 delimiter = '\n' # '\r\n' will also work (see dataReceived)
561
562 __pychecker__ = 'unusednames=params,prefix,channel'
563
564
565 def _reallySendLine(self, line):
566 return basic.LineReceiver.sendLine(self, lowQuote(line) + '\r')
567
568 def sendLine(self, line):
569 if self.lineRate is None:
570 self._reallySendLine(line)
571 else:
572 self._queue.append(line)
573 if not self._queueEmptying:
574 self._sendLine()
575
576 def _sendLine(self):
577 if self._queue:
578 self._reallySendLine(self._queue.pop(0))
579 self._queueEmptying = reactor.callLater(self.lineRate,
580 self._sendLine)
581 else:
582 self._queueEmptying = None
583
584
585 ### Interface level client->user output methods
586 ###
587 ### You'll want to override these.
588
589 ### Methods relating to the server itself
590
591 def created(self, when):
592 """Called with creation date information about the server, usually at lo gon.
593
594 @type when: C{str}
595 @param when: A string describing when the server was created, probably.
596 """
597
598 def yourHost(self, info):
599 """Called with daemon information about the server, usually at logon.
600
601 @type info: C{str}
602 @param when: A string describing what software the server is running, pr obably.
603 """
604
605 def myInfo(self, servername, version, umodes, cmodes):
606 """Called with information about the server, usually at logon.
607
608 @type servername: C{str}
609 @param servername: The hostname of this server.
610
611 @type version: C{str}
612 @param version: A description of what software this server runs.
613
614 @type umodes: C{str}
615 @param umodes: All the available user modes.
616
617 @type cmodes: C{str}
618 @param cmodes: All the available channel modes.
619 """
620
621 def luserClient(self, info):
622 """Called with information about the number of connections, usually at l ogon.
623
624 @type info: C{str}
625 @param info: A description of the number of clients and servers
626 connected to the network, probably.
627 """
628
629 def bounce(self, info):
630 """Called with information about where the client should reconnect.
631
632 @type info: C{str}
633 @param info: A plaintext description of the address that should be
634 connected to.
635 """
636
637 def isupport(self, options):
638 """Called with various information about what the server supports.
639
640 @type options: C{list} of C{str}
641 @param options: Descriptions of features or limits of the server, possib ly
642 in the form "NAME=VALUE".
643 """
644
645 def luserChannels(self, channels):
646 """Called with the number of channels existant on the server.
647
648 @type channels: C{int}
649 """
650
651 def luserOp(self, ops):
652 """Called with the number of ops logged on to the server.
653
654 @type ops: C{int}
655 """
656
657 def luserMe(self, info):
658 """Called with information about the server connected to.
659
660 @type info: C{str}
661 @param info: A plaintext string describing the number of users and serve rs
662 connected to this server.
663 """
664
665 ### Methods involving me directly
666
667 def privmsg(self, user, channel, message):
668 """Called when I have a message from a user to me or a channel.
669 """
670 pass
671
672 def joined(self, channel):
673 """Called when I finish joining a channel.
674
675 channel has the starting character (# or &) intact.
676 """
677 pass
678
679 def left(self, channel):
680 """Called when I have left a channel.
681
682 channel has the starting character (# or &) intact.
683 """
684 pass
685
686 def noticed(self, user, channel, message):
687 """Called when I have a notice from a user to me or a channel.
688
689 By default, this is equivalent to IRCClient.privmsg, but if your
690 client makes any automated replies, you must override this!
691 From the RFC::
692
693 The difference between NOTICE and PRIVMSG is that
694 automatic replies MUST NEVER be sent in response to a
695 NOTICE message. [...] The object of this rule is to avoid
696 loops between clients automatically sending something in
697 response to something it received.
698 """
699 self.privmsg(user, channel, message)
700
701 def modeChanged(self, user, channel, set, modes, args):
702 """Called when a channel's modes are changed
703
704 @type user: C{str}
705 @param user: The user and hostmask which instigated this change.
706
707 @type channel: C{str}
708 @param channel: The channel for which the modes are changing.
709
710 @type set: C{bool} or C{int}
711 @param set: true if the mode is being added, false if it is being
712 removed.
713
714 @type modes: C{str}
715 @param modes: The mode or modes which are being changed.
716
717 @type args: C{tuple}
718 @param args: Any additional information required for the mode
719 change.
720 """
721
722 def pong(self, user, secs):
723 """Called with the results of a CTCP PING query.
724 """
725 pass
726
727 def signedOn(self):
728 """Called after sucessfully signing on to the server.
729 """
730 pass
731
732 def kickedFrom(self, channel, kicker, message):
733 """Called when I am kicked from a channel.
734 """
735 pass
736
737 def nickChanged(self, nick):
738 """Called when my nick has been changed.
739 """
740 self.nickname = nick
741
742
743 ### Things I observe other people doing in a channel.
744
745 def userJoined(self, user, channel):
746 """Called when I see another user joining a channel.
747 """
748 pass
749
750 def userLeft(self, user, channel):
751 """Called when I see another user leaving a channel.
752 """
753 pass
754
755 def userQuit(self, user, quitMessage):
756 """Called when I see another user disconnect from the network.
757 """
758 pass
759
760 def userKicked(self, kickee, channel, kicker, message):
761 """Called when I observe someone else being kicked from a channel.
762 """
763 pass
764
765 def action(self, user, channel, data):
766 """Called when I see a user perform an ACTION on a channel.
767 """
768 pass
769
770 def topicUpdated(self, user, channel, newTopic):
771 """In channel, user changed the topic to newTopic.
772
773 Also called when first joining a channel.
774 """
775 pass
776
777 def userRenamed(self, oldname, newname):
778 """A user changed their name from oldname to newname.
779 """
780 pass
781
782 ### Information from the server.
783
784 def receivedMOTD(self, motd):
785 """I received a message-of-the-day banner from the server.
786
787 motd is a list of strings, where each string was sent as a seperate
788 message from the server. To display, you might want to use::
789
790 string.join(motd, '\\n')
791
792 to get a nicely formatted string.
793 """
794 pass
795
796 ### user input commands, client->server
797 ### Your client will want to invoke these.
798
799 def join(self, channel, key=None):
800 if channel[0] not in '&#!+': channel = '#' + channel
801 if key:
802 self.sendLine("JOIN %s %s" % (channel, key))
803 else:
804 self.sendLine("JOIN %s" % (channel,))
805
806 def leave(self, channel, reason=None):
807 if channel[0] not in '&#!+': channel = '#' + channel
808 if reason:
809 self.sendLine("PART %s :%s" % (channel, reason))
810 else:
811 self.sendLine("PART %s" % (channel,))
812
813 def kick(self, channel, user, reason=None):
814 if channel[0] not in '&#!+': channel = '#' + channel
815 if reason:
816 self.sendLine("KICK %s %s :%s" % (channel, user, reason))
817 else:
818 self.sendLine("KICK %s %s" % (channel, user))
819
820 part = leave
821
822 def topic(self, channel, topic=None):
823 """Attempt to set the topic of the given channel, or ask what it is.
824
825 If topic is None, then I sent a topic query instead of trying to set
826 the topic. The server should respond with a TOPIC message containing
827 the current topic of the given channel.
828 """
829 # << TOPIC #xtestx :fff
830 if channel[0] not in '&#!+': channel = '#' + channel
831 if topic != None:
832 self.sendLine("TOPIC %s :%s" % (channel, topic))
833 else:
834 self.sendLine("TOPIC %s" % (channel,))
835
836 def mode(self, chan, set, modes, limit = None, user = None, mask = None):
837 """Change the modes on a user or channel."""
838 if set:
839 line = 'MODE %s +%s' % (chan, modes)
840 else:
841 line = 'MODE %s -%s' % (chan, modes)
842 if limit is not None:
843 line = '%s %d' % (line, limit)
844 elif user is not None:
845 line = '%s %s' % (line, user)
846 elif mask is not None:
847 line = '%s %s' % (line, mask)
848 self.sendLine(line)
849
850
851 def say(self, channel, message, length = None):
852 if channel[0] not in '&#!+': channel = '#' + channel
853 self.msg(channel, message, length)
854
855 def msg(self, user, message, length = None):
856 """Send a message to a user or channel.
857
858 @type user: C{str}
859 @param user: The username or channel name to which to direct the
860 message.
861
862 @type message: C{str}
863 @param message: The text to send
864
865 @type length: C{int}
866 @param length: The maximum number of octets to send at a time. This
867 has the effect of turning a single call to msg() into multiple
868 commands to the server. This is useful when long messages may be
869 sent that would otherwise cause the server to kick us off or silently
870 truncate the text we are sending. If None is passed, the entire
871 message is always send in one command.
872 """
873
874 fmt = "PRIVMSG %s :%%s" % (user,)
875
876 if length is None:
877 self.sendLine(fmt % (message,))
878 else:
879 # NOTE: minimumLength really equals len(fmt) - 2 (for '%s') + n
880 # where n is how many bytes sendLine sends to end the line.
881 # n was magic numbered to 2, I think incorrectly
882 minimumLength = len(fmt)
883 if length <= minimumLength:
884 raise ValueError("Maximum length must exceed %d for message "
885 "to %s" % (minimumLength, user))
886 lines = split(message, length - minimumLength)
887 map(lambda line, self=self, fmt=fmt: self.sendLine(fmt % line),
888 lines)
889
890 def notice(self, user, message):
891 self.sendLine("NOTICE %s :%s" % (user, message))
892
893 def away(self, message=''):
894 self.sendLine("AWAY :%s" % message)
895
896 def register(self, nickname, hostname='foo', servername='bar'):
897 if self.password is not None:
898 self.sendLine("PASS %s" % self.password)
899 self.setNick(nickname)
900 if self.username is None:
901 self.username = nickname
902 self.sendLine("USER %s %s %s :%s" % (self.username, hostname, servername , self.realname))
903
904 def setNick(self, nickname):
905 self.nickname = nickname
906 self.sendLine("NICK %s" % nickname)
907
908 def quit(self, message = ''):
909 self.sendLine("QUIT :%s" % message)
910
911 ### user input commands, client->client
912
913 def me(self, channel, action):
914 """Strike a pose.
915 """
916 if channel[0] not in '&#!+': channel = '#' + channel
917 self.ctcpMakeQuery(channel, [('ACTION', action)])
918
919 _pings = None
920 _MAX_PINGRING = 12
921
922 def ping(self, user, text = None):
923 """Measure round-trip delay to another IRC client.
924 """
925 if self._pings is None:
926 self._pings = {}
927
928 if text is None:
929 chars = string.letters + string.digits + string.punctuation
930 key = ''.join([random.choice(chars) for i in range(12)])
931 else:
932 key = str(text)
933 self._pings[(user, key)] = time.time()
934 self.ctcpMakeQuery(user, [('PING', key)])
935
936 if len(self._pings) > self._MAX_PINGRING:
937 # Remove some of the oldest entries.
938 byValue = [(v, k) for (k, v) in self._pings.items()]
939 byValue.sort()
940 excess = self._MAX_PINGRING - len(self._pings)
941 for i in xrange(excess):
942 del self._pings[byValue[i][1]]
943
944 def dccSend(self, user, file):
945 if type(file) == types.StringType:
946 file = open(file, 'r')
947
948 size = fileSize(file)
949
950 name = getattr(file, "name", "file@%s" % (id(file),))
951
952 factory = DccSendFactory(file)
953 port = reactor.listenTCP(0, factory, 1)
954
955 raise NotImplementedError,(
956 "XXX!!! Help! I need to bind a socket, have it listen, and tell me its address. "
957 "(and stop accepting once we've made a single connection.)")
958
959 my_address = struct.pack("!I", my_address)
960
961 args = ['SEND', name, my_address, str(port)]
962
963 if not (size is None):
964 args.append(size)
965
966 args = string.join(args, ' ')
967
968 self.ctcpMakeQuery(user, [('DCC', args)])
969
970 def dccResume(self, user, fileName, port, resumePos):
971 """Send a DCC RESUME request to another user."""
972 self.ctcpMakeQuery(user, [
973 ('DCC', ['RESUME', fileName, port, resumePos])])
974
975 def dccAcceptResume(self, user, fileName, port, resumePos):
976 """Send a DCC ACCEPT response to clients who have requested a resume.
977 """
978 self.ctcpMakeQuery(user, [
979 ('DCC', ['ACCEPT', fileName, port, resumePos])])
980
981 ### server->client messages
982 ### You might want to fiddle with these,
983 ### but it is safe to leave them alone.
984
985 def irc_ERR_NICKNAMEINUSE(self, prefix, params):
986 self.register(self.nickname+'_')
987
988 def irc_ERR_PASSWDMISMATCH(self, prefix, params):
989 raise IRCPasswordMismatch("Password Incorrect.")
990
991 def irc_RPL_WELCOME(self, prefix, params):
992 self.signedOn()
993
994 def irc_JOIN(self, prefix, params):
995 nick = string.split(prefix,'!')[0]
996 channel = params[-1]
997 if nick == self.nickname:
998 self.joined(channel)
999 else:
1000 self.userJoined(nick, channel)
1001
1002 def irc_PART(self, prefix, params):
1003 nick = string.split(prefix,'!')[0]
1004 channel = params[0]
1005 if nick == self.nickname:
1006 self.left(channel)
1007 else:
1008 self.userLeft(nick, channel)
1009
1010 def irc_QUIT(self, prefix, params):
1011 nick = string.split(prefix,'!')[0]
1012 self.userQuit(nick, params[0])
1013
1014 def irc_MODE(self, prefix, params):
1015 channel, rest = params[0], params[1:]
1016 set = rest[0][0] == '+'
1017 modes = rest[0][1:]
1018 args = rest[1:]
1019 self.modeChanged(prefix, channel, set, modes, tuple(args))
1020
1021 def irc_PING(self, prefix, params):
1022 self.sendLine("PONG %s" % params[-1])
1023
1024 def irc_PRIVMSG(self, prefix, params):
1025 user = prefix
1026 channel = params[0]
1027 message = params[-1]
1028
1029 if not message: return # don't raise an exception if some idiot sends us a blank message
1030
1031 if message[0]==X_DELIM:
1032 m = ctcpExtract(message)
1033 if m['extended']:
1034 self.ctcpQuery(user, channel, m['extended'])
1035
1036 if not m['normal']:
1037 return
1038
1039 message = string.join(m['normal'], ' ')
1040
1041 self.privmsg(user, channel, message)
1042
1043 def irc_NOTICE(self, prefix, params):
1044 user = prefix
1045 channel = params[0]
1046 message = params[-1]
1047
1048 if message[0]==X_DELIM:
1049 m = ctcpExtract(message)
1050 if m['extended']:
1051 self.ctcpReply(user, channel, m['extended'])
1052
1053 if not m['normal']:
1054 return
1055
1056 message = string.join(m['normal'], ' ')
1057
1058 self.noticed(user, channel, message)
1059
1060 def irc_NICK(self, prefix, params):
1061 nick = string.split(prefix,'!', 1)[0]
1062 if nick == self.nickname:
1063 self.nickChanged(params[0])
1064 else:
1065 self.userRenamed(nick, params[0])
1066
1067 def irc_KICK(self, prefix, params):
1068 """Kicked? Who? Not me, I hope.
1069 """
1070 kicker = string.split(prefix,'!')[0]
1071 channel = params[0]
1072 kicked = params[1]
1073 message = params[-1]
1074 if string.lower(kicked) == string.lower(self.nickname):
1075 # Yikes!
1076 self.kickedFrom(channel, kicker, message)
1077 else:
1078 self.userKicked(kicked, channel, kicker, message)
1079
1080 def irc_TOPIC(self, prefix, params):
1081 """Someone in the channel set the topic.
1082 """
1083 user = string.split(prefix, '!')[0]
1084 channel = params[0]
1085 newtopic = params[1]
1086 self.topicUpdated(user, channel, newtopic)
1087
1088 def irc_RPL_TOPIC(self, prefix, params):
1089 """I just joined the channel, and the server is telling me the current t opic.
1090 """
1091 user = string.split(prefix, '!')[0]
1092 channel = params[1]
1093 newtopic = params[2]
1094 self.topicUpdated(user, channel, newtopic)
1095
1096 def irc_RPL_NOTOPIC(self, prefix, params):
1097 user = string.split(prefix, '!')[0]
1098 channel = params[1]
1099 newtopic = ""
1100 self.topicUpdated(user, channel, newtopic)
1101
1102 def irc_RPL_MOTDSTART(self, prefix, params):
1103 if params[-1].startswith("- "):
1104 params[-1] = params[-1][2:]
1105 self.motd = [params[-1]]
1106
1107 def irc_RPL_MOTD(self, prefix, params):
1108 if params[-1].startswith("- "):
1109 params[-1] = params[-1][2:]
1110 self.motd.append(params[-1])
1111
1112 def irc_RPL_ENDOFMOTD(self, prefix, params):
1113 self.receivedMOTD(self.motd)
1114
1115 def irc_RPL_CREATED(self, prefix, params):
1116 self.created(params[1])
1117
1118 def irc_RPL_YOURHOST(self, prefix, params):
1119 self.yourHost(params[1])
1120
1121 def irc_RPL_MYINFO(self, prefix, params):
1122 info = params[1].split(None, 3)
1123 while len(info) < 4:
1124 info.append(None)
1125 self.myInfo(*info)
1126
1127 def irc_RPL_BOUNCE(self, prefix, params):
1128 # 005 is doubly assigned. Piece of crap dirty trash protocol.
1129 if params[-1] == "are available on this server":
1130 self.isupport(params[1:-1])
1131 else:
1132 self.bounce(params[1])
1133
1134 def irc_RPL_LUSERCLIENT(self, prefix, params):
1135 self.luserClient(params[1])
1136
1137 def irc_RPL_LUSEROP(self, prefix, params):
1138 try:
1139 self.luserOp(int(params[1]))
1140 except ValueError:
1141 pass
1142
1143 def irc_RPL_LUSERCHANNELS(self, prefix, params):
1144 try:
1145 self.luserChannels(int(params[1]))
1146 except ValueError:
1147 pass
1148
1149 def irc_RPL_LUSERME(self, prefix, params):
1150 self.luserMe(params[1])
1151
1152 def irc_unknown(self, prefix, command, params):
1153 pass
1154
1155 ### Receiving a CTCP query from another party
1156 ### It is safe to leave these alone.
1157
1158 def ctcpQuery(self, user, channel, messages):
1159 """Dispatch method for any CTCP queries received.
1160 """
1161 for m in messages:
1162 method = getattr(self, "ctcpQuery_%s" % m[0], None)
1163 if method:
1164 method(user, channel, m[1])
1165 else:
1166 self.ctcpUnknownQuery(user, channel, m[0], m[1])
1167
1168 def ctcpQuery_ACTION(self, user, channel, data):
1169 self.action(user, channel, data)
1170
1171 def ctcpQuery_PING(self, user, channel, data):
1172 nick = string.split(user,"!")[0]
1173 self.ctcpMakeReply(nick, [("PING", data)])
1174
1175 def ctcpQuery_FINGER(self, user, channel, data):
1176 if data is not None:
1177 self.quirkyMessage("Why did %s send '%s' with a FINGER query?"
1178 % (user, data))
1179 if not self.fingerReply:
1180 return
1181
1182 if callable(self.fingerReply):
1183 reply = self.fingerReply()
1184 else:
1185 reply = str(self.fingerReply)
1186
1187 nick = string.split(user,"!")[0]
1188 self.ctcpMakeReply(nick, [('FINGER', reply)])
1189
1190 def ctcpQuery_VERSION(self, user, channel, data):
1191 if data is not None:
1192 self.quirkyMessage("Why did %s send '%s' with a VERSION query?"
1193 % (user, data))
1194
1195 if self.versionName:
1196 nick = string.split(user,"!")[0]
1197 self.ctcpMakeReply(nick, [('VERSION', '%s:%s:%s' %
1198 (self.versionName,
1199 self.versionNum,
1200 self.versionEnv))])
1201
1202 def ctcpQuery_SOURCE(self, user, channel, data):
1203 if data is not None:
1204 self.quirkyMessage("Why did %s send '%s' with a SOURCE query?"
1205 % (user, data))
1206 if self.sourceURL:
1207 nick = string.split(user,"!")[0]
1208 # The CTCP document (Zeuge, Rollo, Mesander 1994) says that SOURCE
1209 # replies should be responded to with the location of an anonymous
1210 # FTP server in host:directory:file format. I'm taking the liberty
1211 # of bringing it into the 21st century by sending a URL instead.
1212 self.ctcpMakeReply(nick, [('SOURCE', self.sourceURL),
1213 ('SOURCE', None)])
1214
1215 def ctcpQuery_USERINFO(self, user, channel, data):
1216 if data is not None:
1217 self.quirkyMessage("Why did %s send '%s' with a USERINFO query?"
1218 % (user, data))
1219 if self.userinfo:
1220 nick = string.split(user,"!")[0]
1221 self.ctcpMakeReply(nick, [('USERINFO', self.userinfo)])
1222
1223 def ctcpQuery_CLIENTINFO(self, user, channel, data):
1224 """A master index of what CTCP tags this client knows.
1225
1226 If no arguments are provided, respond with a list of known tags.
1227 If an argument is provided, provide human-readable help on
1228 the usage of that tag.
1229 """
1230
1231 nick = string.split(user,"!")[0]
1232 if not data:
1233 # XXX: prefixedMethodNames gets methods from my *class*,
1234 # but it's entirely possible that this *instance* has more
1235 # methods.
1236 names = reflect.prefixedMethodNames(self.__class__,
1237 'ctcpQuery_')
1238
1239 self.ctcpMakeReply(nick, [('CLIENTINFO',
1240 string.join(names, ' '))])
1241 else:
1242 args = string.split(data)
1243 method = getattr(self, 'ctcpQuery_%s' % (args[0],), None)
1244 if not method:
1245 self.ctcpMakeReply(nick, [('ERRMSG',
1246 "CLIENTINFO %s :"
1247 "Unknown query '%s'"
1248 % (data, args[0]))])
1249 return
1250 doc = getattr(method, '__doc__', '')
1251 self.ctcpMakeReply(nick, [('CLIENTINFO', doc)])
1252
1253
1254 def ctcpQuery_ERRMSG(self, user, channel, data):
1255 # Yeah, this seems strange, but that's what the spec says to do
1256 # when faced with an ERRMSG query (not a reply).
1257 nick = string.split(user,"!")[0]
1258 self.ctcpMakeReply(nick, [('ERRMSG',
1259 "%s :No error has occoured." % data)])
1260
1261 def ctcpQuery_TIME(self, user, channel, data):
1262 if data is not None:
1263 self.quirkyMessage("Why did %s send '%s' with a TIME query?"
1264 % (user, data))
1265 nick = string.split(user,"!")[0]
1266 self.ctcpMakeReply(nick,
1267 [('TIME', ':%s' %
1268 time.asctime(time.localtime(time.time())))])
1269
1270 def ctcpQuery_DCC(self, user, channel, data):
1271 """Initiate a Direct Client Connection
1272 """
1273
1274 if not data: return
1275 dcctype = data.split(None, 1)[0].upper()
1276 handler = getattr(self, "dcc_" + dcctype, None)
1277 if handler:
1278 if self.dcc_sessions is None:
1279 self.dcc_sessions = []
1280 data = data[len(dcctype)+1:]
1281 handler(user, channel, data)
1282 else:
1283 nick = string.split(user,"!")[0]
1284 self.ctcpMakeReply(nick, [('ERRMSG',
1285 "DCC %s :Unknown DCC type '%s'"
1286 % (data, dcctype))])
1287 self.quirkyMessage("%s offered unknown DCC type %s"
1288 % (user, dcctype))
1289
1290 def dcc_SEND(self, user, channel, data):
1291 # Use splitQuoted for those who send files with spaces in the names.
1292 data = text.splitQuoted(data)
1293 if len(data) < 3:
1294 raise IRCBadMessage, "malformed DCC SEND request: %r" % (data,)
1295
1296 (filename, address, port) = data[:3]
1297
1298 address = dccParseAddress(address)
1299 try:
1300 port = int(port)
1301 except ValueError:
1302 raise IRCBadMessage, "Indecipherable port %r" % (port,)
1303
1304 size = -1
1305 if len(data) >= 4:
1306 try:
1307 size = int(data[3])
1308 except ValueError:
1309 pass
1310
1311 # XXX Should we bother passing this data?
1312 self.dccDoSend(user, address, port, filename, size, data)
1313
1314 def dcc_ACCEPT(self, user, channel, data):
1315 data = text.splitQuoted(data)
1316 if len(data) < 3:
1317 raise IRCBadMessage, "malformed DCC SEND ACCEPT request: %r" % (data ,)
1318 (filename, port, resumePos) = data[:3]
1319 try:
1320 port = int(port)
1321 resumePos = int(resumePos)
1322 except ValueError:
1323 return
1324
1325 self.dccDoAcceptResume(user, filename, port, resumePos)
1326
1327 def dcc_RESUME(self, user, channel, data):
1328 data = text.splitQuoted(data)
1329 if len(data) < 3:
1330 raise IRCBadMessage, "malformed DCC SEND RESUME request: %r" % (data ,)
1331 (filename, port, resumePos) = data[:3]
1332 try:
1333 port = int(port)
1334 resumePos = int(resumePos)
1335 except ValueError:
1336 return
1337 self.dccDoResume(user, filename, port, resumePos)
1338
1339 def dcc_CHAT(self, user, channel, data):
1340 data = text.splitQuoted(data)
1341 if len(data) < 3:
1342 raise IRCBadMessage, "malformed DCC CHAT request: %r" % (data,)
1343
1344 (filename, address, port) = data[:3]
1345
1346 address = dccParseAddress(address)
1347 try:
1348 port = int(port)
1349 except ValueError:
1350 raise IRCBadMessage, "Indecipherable port %r" % (port,)
1351
1352 self.dccDoChat(user, channel, address, port, data)
1353
1354 ### The dccDo methods are the slightly higher-level siblings of
1355 ### common dcc_ methods; the arguments have been parsed for them.
1356
1357 def dccDoSend(self, user, address, port, fileName, size, data):
1358 """Called when I receive a DCC SEND offer from a client.
1359
1360 By default, I do nothing here."""
1361 ## filename = path.basename(arg)
1362 ## protocol = DccFileReceive(filename, size,
1363 ## (user,channel,data),self.dcc_destdir)
1364 ## reactor.clientTCP(address, port, protocol)
1365 ## self.dcc_sessions.append(protocol)
1366 pass
1367
1368 def dccDoResume(self, user, file, port, resumePos):
1369 """Called when a client is trying to resume an offered file
1370 via DCC send. It should be either replied to with a DCC
1371 ACCEPT or ignored (default)."""
1372 pass
1373
1374 def dccDoAcceptResume(self, user, file, port, resumePos):
1375 """Called when a client has verified and accepted a DCC resume
1376 request made by us. By default it will do nothing."""
1377 pass
1378
1379 def dccDoChat(self, user, channel, address, port, data):
1380 pass
1381 #factory = DccChatFactory(self, queryData=(user, channel, data))
1382 #reactor.connectTCP(address, port, factory)
1383 #self.dcc_sessions.append(factory)
1384
1385 #def ctcpQuery_SED(self, user, data):
1386 # """Simple Encryption Doodoo
1387 #
1388 # Feel free to implement this, but no specification is available.
1389 # """
1390 # raise NotImplementedError
1391
1392 def ctcpUnknownQuery(self, user, channel, tag, data):
1393 nick = string.split(user,"!")[0]
1394 self.ctcpMakeReply(nick, [('ERRMSG',
1395 "%s %s: Unknown query '%s'"
1396 % (tag, data, tag))])
1397
1398 log.msg("Unknown CTCP query from %s: %s %s\n"
1399 % (user, tag, data))
1400
1401 def ctcpMakeReply(self, user, messages):
1402 """Send one or more X{extended messages} as a CTCP reply.
1403
1404 @type messages: a list of extended messages. An extended
1405 message is a (tag, data) tuple, where 'data' may be C{None}.
1406 """
1407 self.notice(user, ctcpStringify(messages))
1408
1409 ### client CTCP query commands
1410
1411 def ctcpMakeQuery(self, user, messages):
1412 """Send one or more X{extended messages} as a CTCP query.
1413
1414 @type messages: a list of extended messages. An extended
1415 message is a (tag, data) tuple, where 'data' may be C{None}.
1416 """
1417 self.msg(user, ctcpStringify(messages))
1418
1419 ### Receiving a response to a CTCP query (presumably to one we made)
1420 ### You may want to add methods here, or override UnknownReply.
1421
1422 def ctcpReply(self, user, channel, messages):
1423 """Dispatch method for any CTCP replies received.
1424 """
1425 for m in messages:
1426 method = getattr(self, "ctcpReply_%s" % m[0], None)
1427 if method:
1428 method(user, channel, m[1])
1429 else:
1430 self.ctcpUnknownReply(user, channel, m[0], m[1])
1431
1432 def ctcpReply_PING(self, user, channel, data):
1433 nick = user.split('!', 1)[0]
1434 if (not self._pings) or (not self._pings.has_key((nick, data))):
1435 raise IRCBadMessage,\
1436 "Bogus PING response from %s: %s" % (user, data)
1437
1438 t0 = self._pings[(nick, data)]
1439 self.pong(user, time.time() - t0)
1440
1441 def ctcpUnknownReply(self, user, channel, tag, data):
1442 """Called when a fitting ctcpReply_ method is not found.
1443
1444 XXX: If the client makes arbitrary CTCP queries,
1445 this method should probably show the responses to
1446 them instead of treating them as anomolies.
1447 """
1448 log.msg("Unknown CTCP reply from %s: %s %s\n"
1449 % (user, tag, data))
1450
1451 ### Error handlers
1452 ### You may override these with something more appropriate to your UI.
1453
1454 def badMessage(self, line, excType, excValue, tb):
1455 """When I get a message that's so broken I can't use it.
1456 """
1457 log.msg(line)
1458 log.msg(string.join(traceback.format_exception(excType,
1459 excValue,
1460 tb),''))
1461
1462 def quirkyMessage(self, s):
1463 """This is called when I receive a message which is peculiar,
1464 but not wholly indecipherable.
1465 """
1466 log.msg(s + '\n')
1467
1468 ### Protocool methods
1469
1470 def connectionMade(self):
1471 self._queue = []
1472 if self.performLogin:
1473 self.register(self.nickname)
1474
1475 def dataReceived(self, data):
1476 basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
1477
1478 def lineReceived(self, line):
1479 line = lowDequote(line)
1480 try:
1481 prefix, command, params = parsemsg(line)
1482 if numeric_to_symbolic.has_key(command):
1483 command = numeric_to_symbolic[command]
1484 self.handleCommand(command, prefix, params)
1485 except IRCBadMessage:
1486 self.badMessage(line, *sys.exc_info())
1487
1488
1489 def handleCommand(self, command, prefix, params):
1490 """Determine the function to call for the given command and call
1491 it with the given arguments.
1492 """
1493 method = getattr(self, "irc_%s" % command, None)
1494 try:
1495 if method is not None:
1496 method(prefix, params)
1497 else:
1498 self.irc_unknown(prefix, command, params)
1499 except:
1500 log.deferr()
1501
1502
1503 def __getstate__(self):
1504 dct = self.__dict__.copy()
1505 dct['dcc_sessions'] = None
1506 dct['_pings'] = None
1507 return dct
1508
1509
1510 def dccParseAddress(address):
1511 if '.' in address:
1512 pass
1513 else:
1514 try:
1515 address = long(address)
1516 except ValueError:
1517 raise IRCBadMessage,\
1518 "Indecipherable address %r" % (address,)
1519 else:
1520 address = (
1521 (address >> 24) & 0xFF,
1522 (address >> 16) & 0xFF,
1523 (address >> 8) & 0xFF,
1524 address & 0xFF,
1525 )
1526 address = '.'.join(map(str,address))
1527 return address
1528
1529
1530 class DccFileReceiveBasic(protocol.Protocol, styles.Ephemeral):
1531 """Bare protocol to receive a Direct Client Connection SEND stream.
1532
1533 This does enough to keep the other guy talking, but you'll want to
1534 extend my dataReceived method to *do* something with the data I get.
1535 """
1536
1537 bytesReceived = 0
1538
1539 def __init__(self, resumeOffset=0):
1540 self.bytesReceived = resumeOffset
1541 self.resume = (resumeOffset != 0)
1542
1543 def dataReceived(self, data):
1544 """Called when data is received.
1545
1546 Warning: This just acknowledges to the remote host that the
1547 data has been received; it doesn't *do* anything with the
1548 data, so you'll want to override this.
1549 """
1550 self.bytesReceived = self.bytesReceived + len(data)
1551 self.transport.write(struct.pack('!i', self.bytesReceived))
1552
1553
1554 class DccSendProtocol(protocol.Protocol, styles.Ephemeral):
1555 """Protocol for an outgoing Direct Client Connection SEND.
1556 """
1557
1558 blocksize = 1024
1559 file = None
1560 bytesSent = 0
1561 completed = 0
1562 connected = 0
1563
1564 def __init__(self, file):
1565 if type(file) is types.StringType:
1566 self.file = open(file, 'r')
1567
1568 def connectionMade(self):
1569 self.connected = 1
1570 self.sendBlock()
1571
1572 def dataReceived(self, data):
1573 # XXX: Do we need to check to see if len(data) != fmtsize?
1574
1575 bytesShesGot = struct.unpack("!I", data)
1576 if bytesShesGot < self.bytesSent:
1577 # Wait for her.
1578 # XXX? Add some checks to see if we've stalled out?
1579 return
1580 elif bytesShesGot > self.bytesSent:
1581 # self.transport.log("DCC SEND %s: She says she has %d bytes "
1582 # "but I've only sent %d. I'm stopping "
1583 # "this screwy transfer."
1584 # % (self.file,
1585 # bytesShesGot, self.bytesSent))
1586 self.transport.loseConnection()
1587 return
1588
1589 self.sendBlock()
1590
1591 def sendBlock(self):
1592 block = self.file.read(self.blocksize)
1593 if block:
1594 self.transport.write(block)
1595 self.bytesSent = self.bytesSent + len(block)
1596 else:
1597 # Nothing more to send, transfer complete.
1598 self.transport.loseConnection()
1599 self.completed = 1
1600
1601 def connectionLost(self, reason):
1602 self.connected = 0
1603 if hasattr(self.file, "close"):
1604 self.file.close()
1605
1606
1607 class DccSendFactory(protocol.Factory):
1608 protocol = DccSendProtocol
1609 def __init__(self, file):
1610 self.file = file
1611
1612 def buildProtocol(self, connection):
1613 p = self.protocol(self.file)
1614 p.factory = self
1615 return p
1616
1617
1618 def fileSize(file):
1619 """I'll try my damndest to determine the size of this file object.
1620 """
1621 size = None
1622 if hasattr(file, "fileno"):
1623 fileno = file.fileno()
1624 try:
1625 stat_ = os.fstat(fileno)
1626 size = stat_[stat.ST_SIZE]
1627 except:
1628 pass
1629 else:
1630 return size
1631
1632 if hasattr(file, "name") and path.exists(file.name):
1633 try:
1634 size = path.getsize(file.name)
1635 except:
1636 pass
1637 else:
1638 return size
1639
1640 if hasattr(file, "seek") and hasattr(file, "tell"):
1641 try:
1642 try:
1643 file.seek(0, 2)
1644 size = file.tell()
1645 finally:
1646 file.seek(0, 0)
1647 except:
1648 pass
1649 else:
1650 return size
1651
1652 return size
1653
1654 class DccChat(basic.LineReceiver, styles.Ephemeral):
1655 """Direct Client Connection protocol type CHAT.
1656
1657 DCC CHAT is really just your run o' the mill basic.LineReceiver
1658 protocol. This class only varies from that slightly, accepting
1659 either LF or CR LF for a line delimeter for incoming messages
1660 while always using CR LF for outgoing.
1661
1662 The lineReceived method implemented here uses the DCC connection's
1663 'client' attribute (provided upon construction) to deliver incoming
1664 lines from the DCC chat via IRCClient's normal privmsg interface.
1665 That's something of a spoof, which you may well want to override.
1666 """
1667
1668 queryData = None
1669 delimiter = CR + NL
1670 client = None
1671 remoteParty = None
1672 buffer = ""
1673
1674 def __init__(self, client, queryData=None):
1675 """Initialize a new DCC CHAT session.
1676
1677 queryData is a 3-tuple of
1678 (fromUser, targetUserOrChannel, data)
1679 as received by the CTCP query.
1680
1681 (To be honest, fromUser is the only thing that's currently
1682 used here. targetUserOrChannel is potentially useful, while
1683 the 'data' argument is soley for informational purposes.)
1684 """
1685 self.client = client
1686 if queryData:
1687 self.queryData = queryData
1688 self.remoteParty = self.queryData[0]
1689
1690 def dataReceived(self, data):
1691 self.buffer = self.buffer + data
1692 lines = string.split(self.buffer, LF)
1693 # Put the (possibly empty) element after the last LF back in the
1694 # buffer
1695 self.buffer = lines.pop()
1696
1697 for line in lines:
1698 if line[-1] == CR:
1699 line = line[:-1]
1700 self.lineReceived(line)
1701
1702 def lineReceived(self, line):
1703 log.msg("DCC CHAT<%s> %s" % (self.remoteParty, line))
1704 self.client.privmsg(self.remoteParty,
1705 self.client.nickname, line)
1706
1707
1708 class DccChatFactory(protocol.ClientFactory):
1709 protocol = DccChat
1710 noisy = 0
1711 def __init__(self, client, queryData):
1712 self.client = client
1713 self.queryData = queryData
1714
1715 def buildProtocol(self, addr):
1716 p = self.protocol(client=self.client, queryData=self.queryData)
1717 p.factory = self
1718
1719 def clientConnectionFailed(self, unused_connector, unused_reason):
1720 self.client.dcc_sessions.remove(self)
1721
1722 def clientConnectionLost(self, unused_connector, unused_reason):
1723 self.client.dcc_sessions.remove(self)
1724
1725
1726 def dccDescribe(data):
1727 """Given the data chunk from a DCC query, return a descriptive string.
1728 """
1729
1730 orig_data = data
1731 data = string.split(data)
1732 if len(data) < 4:
1733 return orig_data
1734
1735 (dcctype, arg, address, port) = data[:4]
1736
1737 if '.' in address:
1738 pass
1739 else:
1740 try:
1741 address = long(address)
1742 except ValueError:
1743 pass
1744 else:
1745 address = (
1746 (address >> 24) & 0xFF,
1747 (address >> 16) & 0xFF,
1748 (address >> 8) & 0xFF,
1749 address & 0xFF,
1750 )
1751 # The mapping to 'int' is to get rid of those accursed
1752 # "L"s which python 1.5.2 puts on the end of longs.
1753 address = string.join(map(str,map(int,address)), ".")
1754
1755 if dcctype == 'SEND':
1756 filename = arg
1757
1758 size_txt = ''
1759 if len(data) >= 5:
1760 try:
1761 size = int(data[4])
1762 size_txt = ' of size %d bytes' % (size,)
1763 except ValueError:
1764 pass
1765
1766 dcc_text = ("SEND for file '%s'%s at host %s, port %s"
1767 % (filename, size_txt, address, port))
1768 elif dcctype == 'CHAT':
1769 dcc_text = ("CHAT for host %s, port %s"
1770 % (address, port))
1771 else:
1772 dcc_text = orig_data
1773
1774 return dcc_text
1775
1776
1777 class DccFileReceive(DccFileReceiveBasic):
1778 """Higher-level coverage for getting a file from DCC SEND.
1779
1780 I allow you to change the file's name and destination directory.
1781 I won't overwrite an existing file unless I've been told it's okay
1782 to do so. If passed the resumeOffset keyword argument I will attempt to
1783 resume the file from that amount of bytes.
1784
1785 XXX: I need to let the client know when I am finished.
1786 XXX: I need to decide how to keep a progress indicator updated.
1787 XXX: Client needs a way to tell me \"Do not finish until I say so.\"
1788 XXX: I need to make sure the client understands if the file cannot be writte n.
1789 """
1790
1791 filename = 'dcc'
1792 fileSize = -1
1793 destDir = '.'
1794 overwrite = 0
1795 fromUser = None
1796 queryData = None
1797
1798 def __init__(self, filename, fileSize=-1, queryData=None,
1799 destDir='.', resumeOffset=0):
1800 DccFileReceiveBasic.__init__(self, resumeOffset=resumeOffset)
1801 self.filename = filename
1802 self.destDir = destDir
1803 self.fileSize = fileSize
1804
1805 if queryData:
1806 self.queryData = queryData
1807 self.fromUser = self.queryData[0]
1808
1809 def set_directory(self, directory):
1810 """Set the directory where the downloaded file will be placed.
1811
1812 May raise OSError if the supplied directory path is not suitable.
1813 """
1814 if not path.exists(directory):
1815 raise OSError(errno.ENOENT, "You see no directory there.",
1816 directory)
1817 if not path.isdir(directory):
1818 raise OSError(errno.ENOTDIR, "You cannot put a file into "
1819 "something which is not a directory.",
1820 directory)
1821 if not os.access(directory, os.X_OK | os.W_OK):
1822 raise OSError(errno.EACCES,
1823 "This directory is too hard to write in to.",
1824 directory)
1825 self.destDir = directory
1826
1827 def set_filename(self, filename):
1828 """Change the name of the file being transferred.
1829
1830 This replaces the file name provided by the sender.
1831 """
1832 self.filename = filename
1833
1834 def set_overwrite(self, boolean):
1835 """May I overwrite existing files?
1836 """
1837 self.overwrite = boolean
1838
1839
1840 # Protocol-level methods.
1841
1842 def connectionMade(self):
1843 dst = path.abspath(path.join(self.destDir,self.filename))
1844 exists = path.exists(dst)
1845 if self.resume and exists:
1846 # I have been told I want to resume, and a file already
1847 # exists - Here we go
1848 self.file = open(dst, 'ab')
1849 log.msg("Attempting to resume %s - starting from %d bytes" %
1850 (self.file, self.file.tell()))
1851 elif self.overwrite or not exists:
1852 self.file = open(dst, 'wb')
1853 else:
1854 raise OSError(errno.EEXIST,
1855 "There's a file in the way. "
1856 "Perhaps that's why you cannot open it.",
1857 dst)
1858
1859 def dataReceived(self, data):
1860 self.file.write(data)
1861 DccFileReceiveBasic.dataReceived(self, data)
1862
1863 # XXX: update a progress indicator here?
1864
1865 def connectionLost(self, reason):
1866 """When the connection is lost, I close the file.
1867 """
1868 self.connected = 0
1869 logmsg = ("%s closed." % (self,))
1870 if self.fileSize > 0:
1871 logmsg = ("%s %d/%d bytes received"
1872 % (logmsg, self.bytesReceived, self.fileSize))
1873 if self.bytesReceived == self.fileSize:
1874 pass # Hooray!
1875 elif self.bytesReceived < self.fileSize:
1876 logmsg = ("%s (Warning: %d bytes short)"
1877 % (logmsg, self.fileSize - self.bytesReceived))
1878 else:
1879 logmsg = ("%s (file larger than expected)"
1880 % (logmsg,))
1881 else:
1882 logmsg = ("%s %d bytes received"
1883 % (logmsg, self.bytesReceived))
1884
1885 if hasattr(self, 'file'):
1886 logmsg = "%s and written to %s.\n" % (logmsg, self.file.name)
1887 if hasattr(self.file, 'close'): self.file.close()
1888
1889 # self.transport.log(logmsg)
1890
1891 def __str__(self):
1892 if not self.connected:
1893 return "<Unconnected DccFileReceive object at %x>" % (id(self),)
1894 from_ = self.transport.getPeer()
1895 if self.fromUser:
1896 from_ = "%s (%s)" % (self.fromUser, from_)
1897
1898 s = ("DCC transfer of '%s' from %s" % (self.filename, from_))
1899 return s
1900
1901 def __repr__(self):
1902 s = ("<%s at %x: GET %s>"
1903 % (self.__class__, id(self), self.filename))
1904 return s
1905
1906
1907 # CTCP constants and helper functions
1908
1909 X_DELIM = chr(001)
1910
1911 def ctcpExtract(message):
1912 """Extract CTCP data from a string.
1913
1914 Returns a dictionary with two items:
1915
1916 - C{'extended'}: a list of CTCP (tag, data) tuples
1917 - C{'normal'}: a list of strings which were not inside a CTCP delimeter
1918 """
1919
1920 extended_messages = []
1921 normal_messages = []
1922 retval = {'extended': extended_messages,
1923 'normal': normal_messages }
1924
1925 messages = string.split(message, X_DELIM)
1926 odd = 0
1927
1928 # X1 extended data X2 nomal data X3 extended data X4 normal...
1929 while messages:
1930 if odd:
1931 extended_messages.append(messages.pop(0))
1932 else:
1933 normal_messages.append(messages.pop(0))
1934 odd = not odd
1935
1936 extended_messages[:] = filter(None, extended_messages)
1937 normal_messages[:] = filter(None, normal_messages)
1938
1939 extended_messages[:] = map(ctcpDequote, extended_messages)
1940 for i in xrange(len(extended_messages)):
1941 m = string.split(extended_messages[i], SPC, 1)
1942 tag = m[0]
1943 if len(m) > 1:
1944 data = m[1]
1945 else:
1946 data = None
1947
1948 extended_messages[i] = (tag, data)
1949
1950 return retval
1951
1952 # CTCP escaping
1953
1954 M_QUOTE= chr(020)
1955
1956 mQuoteTable = {
1957 NUL: M_QUOTE + '0',
1958 NL: M_QUOTE + 'n',
1959 CR: M_QUOTE + 'r',
1960 M_QUOTE: M_QUOTE + M_QUOTE
1961 }
1962
1963 mDequoteTable = {}
1964 for k, v in mQuoteTable.items():
1965 mDequoteTable[v[-1]] = k
1966 del k, v
1967
1968 mEscape_re = re.compile('%s.' % (re.escape(M_QUOTE),), re.DOTALL)
1969
1970 def lowQuote(s):
1971 for c in (M_QUOTE, NUL, NL, CR):
1972 s = string.replace(s, c, mQuoteTable[c])
1973 return s
1974
1975 def lowDequote(s):
1976 def sub(matchobj, mDequoteTable=mDequoteTable):
1977 s = matchobj.group()[1]
1978 try:
1979 s = mDequoteTable[s]
1980 except KeyError:
1981 s = s
1982 return s
1983
1984 return mEscape_re.sub(sub, s)
1985
1986 X_QUOTE = '\\'
1987
1988 xQuoteTable = {
1989 X_DELIM: X_QUOTE + 'a',
1990 X_QUOTE: X_QUOTE + X_QUOTE
1991 }
1992
1993 xDequoteTable = {}
1994
1995 for k, v in xQuoteTable.items():
1996 xDequoteTable[v[-1]] = k
1997
1998 xEscape_re = re.compile('%s.' % (re.escape(X_QUOTE),), re.DOTALL)
1999
2000 def ctcpQuote(s):
2001 for c in (X_QUOTE, X_DELIM):
2002 s = string.replace(s, c, xQuoteTable[c])
2003 return s
2004
2005 def ctcpDequote(s):
2006 def sub(matchobj, xDequoteTable=xDequoteTable):
2007 s = matchobj.group()[1]
2008 try:
2009 s = xDequoteTable[s]
2010 except KeyError:
2011 s = s
2012 return s
2013
2014 return xEscape_re.sub(sub, s)
2015
2016 def ctcpStringify(messages):
2017 """
2018 @type messages: a list of extended messages. An extended
2019 message is a (tag, data) tuple, where 'data' may be C{None}, a
2020 string, or a list of strings to be joined with whitespace.
2021
2022 @returns: String
2023 """
2024 coded_messages = []
2025 for (tag, data) in messages:
2026 if data:
2027 if not isinstance(data, types.StringType):
2028 try:
2029 # data as list-of-strings
2030 data = " ".join(map(str, data))
2031 except TypeError:
2032 # No? Then use it's %s representation.
2033 pass
2034 m = "%s %s" % (tag, data)
2035 else:
2036 m = str(tag)
2037 m = ctcpQuote(m)
2038 m = "%s%s%s" % (X_DELIM, m, X_DELIM)
2039 coded_messages.append(m)
2040
2041 line = string.join(coded_messages, '')
2042 return line
2043
2044
2045 # Constants (from RFC 2812)
2046 RPL_WELCOME = '001'
2047 RPL_YOURHOST = '002'
2048 RPL_CREATED = '003'
2049 RPL_MYINFO = '004'
2050 RPL_BOUNCE = '005'
2051 RPL_USERHOST = '302'
2052 RPL_ISON = '303'
2053 RPL_AWAY = '301'
2054 RPL_UNAWAY = '305'
2055 RPL_NOWAWAY = '306'
2056 RPL_WHOISUSER = '311'
2057 RPL_WHOISSERVER = '312'
2058 RPL_WHOISOPERATOR = '313'
2059 RPL_WHOISIDLE = '317'
2060 RPL_ENDOFWHOIS = '318'
2061 RPL_WHOISCHANNELS = '319'
2062 RPL_WHOWASUSER = '314'
2063 RPL_ENDOFWHOWAS = '369'
2064 RPL_LISTSTART = '321'
2065 RPL_LIST = '322'
2066 RPL_LISTEND = '323'
2067 RPL_UNIQOPIS = '325'
2068 RPL_CHANNELMODEIS = '324'
2069 RPL_NOTOPIC = '331'
2070 RPL_TOPIC = '332'
2071 RPL_INVITING = '341'
2072 RPL_SUMMONING = '342'
2073 RPL_INVITELIST = '346'
2074 RPL_ENDOFINVITELIST = '347'
2075 RPL_EXCEPTLIST = '348'
2076 RPL_ENDOFEXCEPTLIST = '349'
2077 RPL_VERSION = '351'
2078 RPL_WHOREPLY = '352'
2079 RPL_ENDOFWHO = '315'
2080 RPL_NAMREPLY = '353'
2081 RPL_ENDOFNAMES = '366'
2082 RPL_LINKS = '364'
2083 RPL_ENDOFLINKS = '365'
2084 RPL_BANLIST = '367'
2085 RPL_ENDOFBANLIST = '368'
2086 RPL_INFO = '371'
2087 RPL_ENDOFINFO = '374'
2088 RPL_MOTDSTART = '375'
2089 RPL_MOTD = '372'
2090 RPL_ENDOFMOTD = '376'
2091 RPL_YOUREOPER = '381'
2092 RPL_REHASHING = '382'
2093 RPL_YOURESERVICE = '383'
2094 RPL_TIME = '391'
2095 RPL_USERSSTART = '392'
2096 RPL_USERS = '393'
2097 RPL_ENDOFUSERS = '394'
2098 RPL_NOUSERS = '395'
2099 RPL_TRACELINK = '200'
2100 RPL_TRACECONNECTING = '201'
2101 RPL_TRACEHANDSHAKE = '202'
2102 RPL_TRACEUNKNOWN = '203'
2103 RPL_TRACEOPERATOR = '204'
2104 RPL_TRACEUSER = '205'
2105 RPL_TRACESERVER = '206'
2106 RPL_TRACESERVICE = '207'
2107 RPL_TRACENEWTYPE = '208'
2108 RPL_TRACECLASS = '209'
2109 RPL_TRACERECONNECT = '210'
2110 RPL_TRACELOG = '261'
2111 RPL_TRACEEND = '262'
2112 RPL_STATSLINKINFO = '211'
2113 RPL_STATSCOMMANDS = '212'
2114 RPL_ENDOFSTATS = '219'
2115 RPL_STATSUPTIME = '242'
2116 RPL_STATSOLINE = '243'
2117 RPL_UMODEIS = '221'
2118 RPL_SERVLIST = '234'
2119 RPL_SERVLISTEND = '235'
2120 RPL_LUSERCLIENT = '251'
2121 RPL_LUSEROP = '252'
2122 RPL_LUSERUNKNOWN = '253'
2123 RPL_LUSERCHANNELS = '254'
2124 RPL_LUSERME = '255'
2125 RPL_ADMINME = '256'
2126 RPL_ADMINLOC = '257'
2127 RPL_ADMINLOC = '258'
2128 RPL_ADMINEMAIL = '259'
2129 RPL_TRYAGAIN = '263'
2130 ERR_NOSUCHNICK = '401'
2131 ERR_NOSUCHSERVER = '402'
2132 ERR_NOSUCHCHANNEL = '403'
2133 ERR_CANNOTSENDTOCHAN = '404'
2134 ERR_TOOMANYCHANNELS = '405'
2135 ERR_WASNOSUCHNICK = '406'
2136 ERR_TOOMANYTARGETS = '407'
2137 ERR_NOSUCHSERVICE = '408'
2138 ERR_NOORIGIN = '409'
2139 ERR_NORECIPIENT = '411'
2140 ERR_NOTEXTTOSEND = '412'
2141 ERR_NOTOPLEVEL = '413'
2142 ERR_WILDTOPLEVEL = '414'
2143 ERR_BADMASK = '415'
2144 ERR_UNKNOWNCOMMAND = '421'
2145 ERR_NOMOTD = '422'
2146 ERR_NOADMININFO = '423'
2147 ERR_FILEERROR = '424'
2148 ERR_NONICKNAMEGIVEN = '431'
2149 ERR_ERRONEUSNICKNAME = '432'
2150 ERR_NICKNAMEINUSE = '433'
2151 ERR_NICKCOLLISION = '436'
2152 ERR_UNAVAILRESOURCE = '437'
2153 ERR_USERNOTINCHANNEL = '441'
2154 ERR_NOTONCHANNEL = '442'
2155 ERR_USERONCHANNEL = '443'
2156 ERR_NOLOGIN = '444'
2157 ERR_SUMMONDISABLED = '445'
2158 ERR_USERSDISABLED = '446'
2159 ERR_NOTREGISTERED = '451'
2160 ERR_NEEDMOREPARAMS = '461'
2161 ERR_ALREADYREGISTRED = '462'
2162 ERR_NOPERMFORHOST = '463'
2163 ERR_PASSWDMISMATCH = '464'
2164 ERR_YOUREBANNEDCREEP = '465'
2165 ERR_YOUWILLBEBANNED = '466'
2166 ERR_KEYSET = '467'
2167 ERR_CHANNELISFULL = '471'
2168 ERR_UNKNOWNMODE = '472'
2169 ERR_INVITEONLYCHAN = '473'
2170 ERR_BANNEDFROMCHAN = '474'
2171 ERR_BADCHANNELKEY = '475'
2172 ERR_BADCHANMASK = '476'
2173 ERR_NOCHANMODES = '477'
2174 ERR_BANLISTFULL = '478'
2175 ERR_NOPRIVILEGES = '481'
2176 ERR_CHANOPRIVSNEEDED = '482'
2177 ERR_CANTKILLSERVER = '483'
2178 ERR_RESTRICTED = '484'
2179 ERR_UNIQOPPRIVSNEEDED = '485'
2180 ERR_NOOPERHOST = '491'
2181 ERR_NOSERVICEHOST = '492'
2182 ERR_UMODEUNKNOWNFLAG = '501'
2183 ERR_USERSDONTMATCH = '502'
2184
2185 # And hey, as long as the strings are already intern'd...
2186 symbolic_to_numeric = {
2187 "RPL_WELCOME": '001',
2188 "RPL_YOURHOST": '002',
2189 "RPL_CREATED": '003',
2190 "RPL_MYINFO": '004',
2191 "RPL_BOUNCE": '005',
2192 "RPL_USERHOST": '302',
2193 "RPL_ISON": '303',
2194 "RPL_AWAY": '301',
2195 "RPL_UNAWAY": '305',
2196 "RPL_NOWAWAY": '306',
2197 "RPL_WHOISUSER": '311',
2198 "RPL_WHOISSERVER": '312',
2199 "RPL_WHOISOPERATOR": '313',
2200 "RPL_WHOISIDLE": '317',
2201 "RPL_ENDOFWHOIS": '318',
2202 "RPL_WHOISCHANNELS": '319',
2203 "RPL_WHOWASUSER": '314',
2204 "RPL_ENDOFWHOWAS": '369',
2205 "RPL_LISTSTART": '321',
2206 "RPL_LIST": '322',
2207 "RPL_LISTEND": '323',
2208 "RPL_UNIQOPIS": '325',
2209 "RPL_CHANNELMODEIS": '324',
2210 "RPL_NOTOPIC": '331',
2211 "RPL_TOPIC": '332',
2212 "RPL_INVITING": '341',
2213 "RPL_SUMMONING": '342',
2214 "RPL_INVITELIST": '346',
2215 "RPL_ENDOFINVITELIST": '347',
2216 "RPL_EXCEPTLIST": '348',
2217 "RPL_ENDOFEXCEPTLIST": '349',
2218 "RPL_VERSION": '351',
2219 "RPL_WHOREPLY": '352',
2220 "RPL_ENDOFWHO": '315',
2221 "RPL_NAMREPLY": '353',
2222 "RPL_ENDOFNAMES": '366',
2223 "RPL_LINKS": '364',
2224 "RPL_ENDOFLINKS": '365',
2225 "RPL_BANLIST": '367',
2226 "RPL_ENDOFBANLIST": '368',
2227 "RPL_INFO": '371',
2228 "RPL_ENDOFINFO": '374',
2229 "RPL_MOTDSTART": '375',
2230 "RPL_MOTD": '372',
2231 "RPL_ENDOFMOTD": '376',
2232 "RPL_YOUREOPER": '381',
2233 "RPL_REHASHING": '382',
2234 "RPL_YOURESERVICE": '383',
2235 "RPL_TIME": '391',
2236 "RPL_USERSSTART": '392',
2237 "RPL_USERS": '393',
2238 "RPL_ENDOFUSERS": '394',
2239 "RPL_NOUSERS": '395',
2240 "RPL_TRACELINK": '200',
2241 "RPL_TRACECONNECTING": '201',
2242 "RPL_TRACEHANDSHAKE": '202',
2243 "RPL_TRACEUNKNOWN": '203',
2244 "RPL_TRACEOPERATOR": '204',
2245 "RPL_TRACEUSER": '205',
2246 "RPL_TRACESERVER": '206',
2247 "RPL_TRACESERVICE": '207',
2248 "RPL_TRACENEWTYPE": '208',
2249 "RPL_TRACECLASS": '209',
2250 "RPL_TRACERECONNECT": '210',
2251 "RPL_TRACELOG": '261',
2252 "RPL_TRACEEND": '262',
2253 "RPL_STATSLINKINFO": '211',
2254 "RPL_STATSCOMMANDS": '212',
2255 "RPL_ENDOFSTATS": '219',
2256 "RPL_STATSUPTIME": '242',
2257 "RPL_STATSOLINE": '243',
2258 "RPL_UMODEIS": '221',
2259 "RPL_SERVLIST": '234',
2260 "RPL_SERVLISTEND": '235',
2261 "RPL_LUSERCLIENT": '251',
2262 "RPL_LUSEROP": '252',
2263 "RPL_LUSERUNKNOWN": '253',
2264 "RPL_LUSERCHANNELS": '254',
2265 "RPL_LUSERME": '255',
2266 "RPL_ADMINME": '256',
2267 "RPL_ADMINLOC": '257',
2268 "RPL_ADMINLOC": '258',
2269 "RPL_ADMINEMAIL": '259',
2270 "RPL_TRYAGAIN": '263',
2271 "ERR_NOSUCHNICK": '401',
2272 "ERR_NOSUCHSERVER": '402',
2273 "ERR_NOSUCHCHANNEL": '403',
2274 "ERR_CANNOTSENDTOCHAN": '404',
2275 "ERR_TOOMANYCHANNELS": '405',
2276 "ERR_WASNOSUCHNICK": '406',
2277 "ERR_TOOMANYTARGETS": '407',
2278 "ERR_NOSUCHSERVICE": '408',
2279 "ERR_NOORIGIN": '409',
2280 "ERR_NORECIPIENT": '411',
2281 "ERR_NOTEXTTOSEND": '412',
2282 "ERR_NOTOPLEVEL": '413',
2283 "ERR_WILDTOPLEVEL": '414',
2284 "ERR_BADMASK": '415',
2285 "ERR_UNKNOWNCOMMAND": '421',
2286 "ERR_NOMOTD": '422',
2287 "ERR_NOADMININFO": '423',
2288 "ERR_FILEERROR": '424',
2289 "ERR_NONICKNAMEGIVEN": '431',
2290 "ERR_ERRONEUSNICKNAME": '432',
2291 "ERR_NICKNAMEINUSE": '433',
2292 "ERR_NICKCOLLISION": '436',
2293 "ERR_UNAVAILRESOURCE": '437',
2294 "ERR_USERNOTINCHANNEL": '441',
2295 "ERR_NOTONCHANNEL": '442',
2296 "ERR_USERONCHANNEL": '443',
2297 "ERR_NOLOGIN": '444',
2298 "ERR_SUMMONDISABLED": '445',
2299 "ERR_USERSDISABLED": '446',
2300 "ERR_NOTREGISTERED": '451',
2301 "ERR_NEEDMOREPARAMS": '461',
2302 "ERR_ALREADYREGISTRED": '462',
2303 "ERR_NOPERMFORHOST": '463',
2304 "ERR_PASSWDMISMATCH": '464',
2305 "ERR_YOUREBANNEDCREEP": '465',
2306 "ERR_YOUWILLBEBANNED": '466',
2307 "ERR_KEYSET": '467',
2308 "ERR_CHANNELISFULL": '471',
2309 "ERR_UNKNOWNMODE": '472',
2310 "ERR_INVITEONLYCHAN": '473',
2311 "ERR_BANNEDFROMCHAN": '474',
2312 "ERR_BADCHANNELKEY": '475',
2313 "ERR_BADCHANMASK": '476',
2314 "ERR_NOCHANMODES": '477',
2315 "ERR_BANLISTFULL": '478',
2316 "ERR_NOPRIVILEGES": '481',
2317 "ERR_CHANOPRIVSNEEDED": '482',
2318 "ERR_CANTKILLSERVER": '483',
2319 "ERR_RESTRICTED": '484',
2320 "ERR_UNIQOPPRIVSNEEDED": '485',
2321 "ERR_NOOPERHOST": '491',
2322 "ERR_NOSERVICEHOST": '492',
2323 "ERR_UMODEUNKNOWNFLAG": '501',
2324 "ERR_USERSDONTMATCH": '502',
2325 }
2326
2327 numeric_to_symbolic = {}
2328 for k, v in symbolic_to_numeric.items():
2329 numeric_to_symbolic[v] = k
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698