PageRenderTime 126ms CodeModel.GetById 100ms app.highlight 23ms RepoModel.GetById 0ms app.codeStats 1ms

/circuits/net/protocols/irc.py

https://bitbucket.org/prologic/circuits/
Python | 487 lines | 460 code | 7 blank | 20 comment | 3 complexity | 6722d8d9cf2cf1431a498f01a611f3ea MD5 | raw file
  1# Module:   irc
  2# Date:     04th August 2004
  3# Author:   James Mills <prologic@shortcircuit.net.au>
  4
  5"""Internet Relay Chat Protocol
  6
  7This module implements the Internet Relay Chat Protocol
  8or commonly known as IRC.
  9
 10This module can be used in both server and client
 11implementations.
 12"""
 13
 14import re
 15import time
 16
 17from circuits.net.sockets import Write
 18from circuits.core import Event, Component
 19
 20from .line import LP
 21
 22###
 23### Supporting Functions
 24###
 25
 26def strip(s, color=False):
 27    """strip(s, color=False) -> str
 28
 29    Strips the : from the start of a string
 30    and optionally also strips all colors if
 31    color is True.
 32    """
 33
 34    if len(s) > 0:
 35        if s[0] == ":":
 36            s = s[1:]
 37    if color:
 38        s = s.replace("\x01", "")
 39        s = s.replace("\x02", "")
 40    return s
 41
 42def sourceJoin(nick, ident, host):
 43    """sourceJoin(nick, ident, host) -> str
 44
 45    Join a source previously split by sourceSplit
 46    and join it back together inserting the ! and @
 47    appropiately.
 48    """
 49
 50    return "%s!%s@%s" % (nick, ident, host)
 51
 52def sourceSplit(source):
 53    """sourceSplit(source) -> str, str, str
 54
 55    Split the given source into its parts.
 56
 57    source must be of the form: nick!ident@host
 58
 59    Example:
 60    >>> nick, ident, host, = sourceSplit("Joe!Blogs@localhost")
 61    """
 62
 63    m = re.match(
 64            "(?P<nick>[^!].*)!(?P<ident>.*)@(?P<host>.*)",
 65            source)
 66
 67    if m is not None:
 68        d = m.groupdict()
 69        return d["nick"], d["ident"], d["host"]
 70    else:
 71        return source, None, None
 72
 73###
 74### IRC Commands
 75###
 76
 77class Command(Event):
 78    """Command (Event)"""
 79
 80    def __init__(self, *args, **kwargs):
 81        super(Command, self).__init__(*args, **kwargs)
 82
 83        self.channel = self.__class__.__name__
 84
 85class RAW(Command):
 86    """RAW command"""
 87
 88class PASS(Command):
 89    """PASS command"""
 90
 91class USER(Command):
 92    """USER command"""
 93
 94class NICK(Command):
 95    """NICK command"""
 96
 97class PING(Command):
 98    """PING command"""
 99
100class PONG(Command):
101    """PONG command"""
102
103class QUIT(Command):
104    """QUIT command"""
105
106class JOIN(Command):
107    """JOIN command"""
108
109class PART(Command):
110    """PART command"""
111
112class PRIVMSG(Command):
113    """PRIVMSG command"""
114
115class NOTICE(Command):
116    """NOTICE command"""
117
118class CTCP(Command):
119    """CTCP command"""
120
121class CTCPREPLY(Command):
122    """CTCPREPLY command"""
123
124class KICK(Command):
125    """KICK command"""
126
127class TOPIC(Command):
128    """TOPIC command"""
129
130class MODE(Command):
131    """MODE command"""
132
133class INVITE(Command):
134    """INVITE command"""
135
136class NAMES(Command):
137    """NAMES command"""
138
139###
140### IRC Responses
141###
142
143class Response(Event):
144    """Response (Event)"""
145
146    def __init__(self, *args, **kwargs):
147        super(Response, self).__init__(*args, **kwargs)
148
149class Numeric(Response):
150    """Numeric response"""
151
152class Ping(Response):
153    """Ping response"""
154
155class Ctcp(Response):
156    """Ctcp response"""
157
158class Message(Response):
159    """Message response"""
160
161class Notice(Response):
162    """Notice response"""
163
164class Join(Response):
165    """Join response"""
166
167class Part(Response):
168    """Part response"""
169
170class Quit(Response):
171    """Quit response"""
172
173class Nick(Response):
174    """Nick response"""
175
176class Mode(Response):
177    """Mode response"""
178
179###
180### Protocol Component(s)
181###
182
183class IRC(Component):
184    """IRC Protocol Component
185
186    Creates a new IRC Component instance that implements the IRC Protocol.
187    Incoming messages are handled by the "read" Event Handler, parsed and
188    processed with appropriate Events created and exposed to the rest of
189    te system to listen to and handle.
190
191    @note: This Component must be used in conjunction with a Component that
192           exposes Read Events on a "read" Channel.
193    """
194
195    def __init__(self, *args, **kwargs):
196        super(IRC, self).__init__(*args, **kwargs)
197        LP(**kwargs).register(self)
198
199    ###
200    ### IRC Command Event Handlers
201    ###
202
203    def RAW(self, data):
204        self.fire(Write("%s\r\n" % data))
205
206    def PASS(self, password):
207        self.fire(RAW("PASS %s" % password))
208
209    def USER(self, ident, host, server, name):
210        self.fire(RAW("USER %s \"%s\" \"%s\" :%s" % (
211            ident, host, server, name)))
212
213    def NICK(self, nick):
214        self.fire(RAW("NICK %s" % nick))
215
216    def PING(self, server):
217        self.fire(RAW("PING :%s" % server))
218
219    def PONG(self, server):
220        self.fire(RAW("PONG :%s" % server))
221
222    def QUIT(self, message="Leaving"):
223        self.fire(RAW("QUIT :%s" % message))
224
225    def JOIN(self, channel, key=None):
226        if key is None:
227            self.fire(RAW("JOIN %s" % channel))
228        else:
229            self.fire(RAW("JOIN %s %s" % (channel, key)))
230
231    def PART(self, channel, message="Leaving"):
232        self.fire(RAW("PART %s :%s" % (channel, message)))
233
234    def PRIVMSG(self, target, message):
235        self.fire(RAW("PRIVMSG %s :%s" % (target, message)))
236
237    def NOTICE(self, target, message):
238        self.fire(RAW("NOTICE %s :%s" % (target, message)))
239
240    def CTCP(self, target, type, message):
241        self.fire(PRIVMSG(target, "%s %s" % (type, message)))
242
243    def CTCPREPLY(self, target, type, message):
244        self.fire(NOTICE(target, "%s %s" % (type, message)))
245
246    def KICK(self, channel, target, message=""):
247        self.fire(RAW("KICK %s %s :%s" % (channel, target, message)))
248
249    def TOPIC(self, channel, topic):
250        self.fire(RAW("TOPIC %s :%s" % (channel, topic)))
251
252    def MODE(self, modes, channel=None):
253        if channel is None:
254            self.fire(RAW("MODE :%s" % modes))
255        else:
256            self.fire(RAW("MODE %s :%s" % (channel, modes)))
257
258    def INVITE(self, target, channel):
259        self.fire(RAW("INVITE %s %s" % (target, channel)))
260
261    def NAMES(self, channel=None):
262        if channel:
263            self.fire(RAW("NAMES %s" % channel))
264        else:
265            self.fire(RAW("NAMES"))
266
267    ###
268    ### Event Processing
269    ###
270
271    def line(self, line):
272        """Line Event Handler
273
274        Process a line of text and generate the appropiate
275        event. This must not be overridden by sub-classes,
276        if it is, this must be explitetly called by the
277        sub-class. Other Components may however listen to
278        this event and process custom IRC events.
279        """
280
281        tokens = line.split(" ")
282
283        if tokens[0] == "PING":
284            self.fire(Ping(strip(tokens[1])))
285
286        elif re.match("[0-9]+", tokens[1]):
287            source = strip(tokens[0])
288            target = tokens[2]
289
290            numeric = int(tokens[1])
291            if tokens[3].startswith(":"):
292                arg = None
293                message = strip(" ".join(tokens[3:]))
294            else:
295                arg = tokens[3]
296                message = strip(" ".join(tokens[4:]))
297
298            self.fire(Numeric(source, target, numeric, arg, message))
299
300        elif tokens[1] == "PRIVMSG":
301            source = sourceSplit(strip(tokens[0]))
302            target = tokens[2]
303            message = strip(" ".join(tokens[3:]))
304
305            if message and message[0] == "":
306                tokens = strip(message, color=True).split(" ")
307                type = tokens[0]
308                message = " ".join(tokens[1:])
309                self.fire(Ctcp(source, target, type, message))
310            else:
311                self.fire(Message(source, target, message))
312
313        elif tokens[1] == "NOTICE":
314            source = sourceSplit(strip(tokens[0]))
315            target = tokens[2]
316            message = strip(" ".join(tokens[3:]))
317            self.fire(Notice(source, target, message))
318
319        elif tokens[1] == "JOIN":
320            source = sourceSplit(strip(tokens[0]))
321            channel = strip(tokens[2])
322            self.fire(Join(source, channel))
323
324        elif tokens[1] == "PART":
325            source = sourceSplit(strip(tokens[0]))
326            channel = strip(tokens[2])
327            message = strip(" ".join(tokens[3:]))
328            self.fire(Part(source, channel, message))
329
330        elif tokens[1] == "QUIT":
331            source = sourceSplit(strip(tokens[0]))
332            message = strip(" ".join(tokens[2:]))
333            self.fire(Quit(source, message))
334
335        elif tokens[1] == "NICK":
336            source = sourceSplit(strip(tokens[0]))
337            newNick = strip(tokens[2])
338
339            self.fire(Nick(source, newNick))
340
341        elif tokens[1] == "MODE":
342            source = sourceSplit(strip(tokens[0]))
343            target = tokens[2]
344            modes = strip(" ".join(tokens[3:]))
345            self.fire(Mode(source, target, modes))
346
347    ###
348    ### Default Events
349    ###
350
351    def ping(self, server):
352        """Ping Event
353
354        This is a default event ro respond to Ping Events
355        by sending out a Pong in response. Sub-classes
356        may override this, but be sure to respond to
357        Ping Events by either explitetly calling this method
358        or sending your own Pong reponse.
359        """
360
361        self.fire(PONG(server))
362
363###
364### Errors and Numeric Replies
365###
366
367RPL_WELCOME = 1
368RPL_YOURHOST = 2
369
370RPL_TRACELINK = 200
371RPL_TRACECONNECTING = 201
372RPL_TRACEHANDSHAKE = 202
373RPL_TRACEUNKNOWN = 203
374RPL_TRACEOPERATOR = 204
375RPL_TRACEUSER = 205
376RPL_TRACESERVER = 206
377RPL_TRACENEWTYPE = 208
378RPL_TRACELOG = 261
379RPL_STATSLINKINFO = 211
380RPL_STATSCOMMANDS = 212
381RPL_STATSCLINE = 213
382RPL_STATSNLINE = 214
383RPL_STATSILINE = 215
384RPL_STATSKLINE = 216
385RPL_STATSYLINE = 218
386RPL_ENDOFSTATS = 219
387RPL_STATSLLINE = 241
388RPL_STATSUPTIME = 242
389RPL_STATSOLINE = 243
390RPL_STATSHLINE = 244
391RPL_UMODEIS = 221
392RPL_LUSERCLIENT = 251
393RPL_LUSEROP = 252
394RPL_LUSERUNKNOWN = 253
395RPL_LUSERCHANNELS = 254
396RPL_LUSERME = 255
397RPL_ADMINME = 256
398RPL_ADMINLOC1 = 257
399RPL_ADMINLOC2 = 258
400RPL_ADMINEMAIL = 259
401
402RPL_NONE = 300
403RPL_USERHOST = 302
404RPL_ISON = 303
405RPL_AWAY = 301
406RPL_UNAWAY = 305
407RPL_NOWAWAY = 306
408RPL_WHOISUSER = 311
409RPL_WHOISSERVER = 312
410RPL_WHOISOPERATOR = 313
411RPL_WHOISIDLE = 317
412RPL_ENDOFWHOIS = 318
413RPL_WHOISCHANNELS = 319
414RPL_WHOWASUSER = 314
415RPL_ENDOFWHOWAS = 369
416RPL_LIST = 322
417RPL_LISTEND = 323
418RPL_CHANNELMODEIS = 324
419RPL_NOTOPIC = 331
420RPL_TOPIC = 332
421RPL_INVITING = 341
422RPL_SUMMONING = 342
423RPL_VERSION = 351
424RPL_WHOREPLY = 352
425RPL_ENDOFWHO = 315
426RPL_NAMREPLY = 353
427RPL_ENDOFNAMES = 366
428RPL_LINKS = 364
429RPL_ENDOFLINKS = 365
430RPL_BANLIST = 367
431RPL_ENDOFBANLIST = 368
432RPL_INFO = 371
433RPL_ENDOFINFO = 374
434RPL_MOTDSTART = 375
435RPL_MOTD = 372
436RPL_ENDOFMOTD = 376
437RPL_YOUREOPER = 381
438RPL_REHASHING = 382
439RPL_TIME = 391
440RPL_USERSSTART = 392
441RPL_USERS = 393
442RPL_ENDOFUSERS = 394
443RPL_NOUSERS = 395
444
445ERR_NOSUCHNICK = 401
446ERR_NOSUCHSERVER = 402
447ERR_NOSUCHCHANNEL = 403
448ERR_CANNOTSENDTOCHAN = 404
449ERR_TOOMANYCHANNELS = 405
450ERR_WASNOSUCHNICK = 406
451ERR_TOOMANYTARGETS = 407
452ERR_NOORIGIN = 409
453ERR_NORECIPIENT = 411
454ERR_NOTEXTTOSEND = 412
455ERR_NOTOPLEVEL = 413
456ERR_WILDTOPLEVEL = 414
457ERR_UNKNOWNCOMMAND = 421
458ERR_NOMOTD = 422
459ERR_NOADMININFO = 423
460ERR_FILEERROR = 424
461ERR_NONICKNAMEGIVEN = 431
462ERR_ERRONEUSNICKNAME = 432
463ERR_NICKNAMEINUSE = 433
464ERR_NICKCOLLISION = 436
465ERR_NOTONCHANNEL = 442
466ERR_USERONCHANNEL = 443
467ERR_NOLOGIN = 444
468ERR_SUMMONDISABLED = 445
469ERR_USERSDISABLED = 446
470ERR_NOTREGISTERED = 451
471ERR_NEEDMOREPARAMS = 461
472ERR_ALREADYREGISTRED = 462
473ERR_PASSWDMISMATCH = 464
474ERR_YOUREBANNEDCREEP = 465
475ERR_KEYSET = 467
476ERR_CHANNELISFULL = 471
477ERR_UNKNOWNMODE = 472
478ERR_INVITEONLYCHAN = 473
479ERR_BANNEDFROMCHAN = 474
480ERR_BADCHANNELKEY = 475
481ERR_NOPRIVILEGES = 481
482ERR_CHANOPRIVSNEEDED = 482
483ERR_CANTKILLSERVER = 483
484ERR_NOOPERHOST = 491
485
486ERR_UMODEUNKNOWNFLAG = 501
487ERR_USERSDONTMATCH = 502