pvl/irker/irc.py
changeset 224 ed410776effd
parent 223 6842794c20e8
child 225 3c2d0dd42045
--- a/pvl/irker/irc.py	Tue Feb 19 19:28:40 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,462 +0,0 @@
-"""
-    IRC client, dispatching irker messages.
-"""
-
-from twisted.internet import reactor, interfaces, protocol, defer, error
-from twisted.words.protocols import irc
-
-from twisted.internet import endpoints
-
-from twisted.python import log
-
-PORT = 6667
-
-def url2endpoint (reactor, url) :
-    """
-        Turn given urlparse URL into an endpoint.
-
-        Raises KeyError on unknown scheme.
-    """
-
-    SCHEMES = {
-            'irc':      lambda : endpoints.TCP4ClientEndpoint(reactor, url.hostname, url.port or PORT),
-    }
-    
-    return SCHEMES[url.scheme]()
-
-def normalize (name) :
-    """
-        Normalize a channel/nickname for comparisons in IRC.
-    """
-
-    return name.lower()
-
-class IRCError (Exception) :
-    """
-        A handled protocol error.
-    """
-
-    pass
-
-class IRCChannel (object) :
-    """
-        A joined channel on an IRC server.
-    """
-
-    ENCODING = 'utf-8'
-
-    def __init__ (self, client, channel, encoding=ENCODING) :
-        self.client = client
-        self.channel = channel
-
-        self.encoding = encoding
-
-        # TODO: separate join/part state
-        #self.joining = self.parting = None
-
-    def encode (self, unicode) :
-        if unicode :
-            return unicode.encode(self.encoding)
-        else :
-            return None
-
-    def privmsg (self, *msgs) :
-        for msg in msgs :
-            # XXX: encode
-            self.client.msg(self.channel, self.encode(msg))
-
-    def notice (self, *msgs) :
-        for msg in msgs :
-            self.client.notice(self.channel, self.encode(msg))
-
-    def part (self, msg=None) :
-        """
-            Remove channel from our list of channels.
-        """
-        
-        # send the PART
-        self.client.leave(self.channel, self.encode(msg))
-
-        # ...and then immediately forget the channel, in case we rejoin before getting the part back
-        # TODO: self.joining/parting
-        self.client._close_channel(self.channel)
-
-    def errback (self, failure) :
-        """
-            Fail any pending requests.
-        """
-
-        log.msg('IRCChannel.errback', self, failure)
-
-    def __str__ (self) :
-        return self.client.url(self.channel)
-
-class IRCClient (irc.IRCClient) :
-    """
-        A connection to an IRC server with a specific, requested nickname.
-
-        Joins to channels.
-    """
-
-    performLogin = False
-
-    def __init__ (self, factory) :
-        self.factory = factory
-        
-        self.nickname = None
-        self.hostname = None
-
-        self._registering = None
-        self._channels = { }
-
-        # TODO: smarter/configurable queueing?
-        self.lineRate = 1.0
-
-    def connectionMade (self) :
-        self.hostname = self.transport.getPeer().host
-        self.transport.logPrefix = self.logPrefix
-
-        log.msg("connectionMade", self, self.transport)
-        irc.IRCClient.connectionMade(self)
-
-    def sendLine (self, line) :
-        irc.IRCClient.sendLine(self, line)
-
-        log.msg(">>>", line)
-
-    def lineReceived (self, line) :
-        log.msg("<<<", line)
-
-        irc.IRCClient.lineReceived(self, line)
-        
-    ## Register
-    def register (self, nickname, username=None, password=None) :
-        """
-            Register to the server, choosing a nickname based on the given nickname.
-
-            Returns a Deferred that callbacks with our actual nickname once we have registered, or errbacks with an IRCError.
-        """
-
-        if self._registering :
-            raise Exception("register: already registering")
-
-        self.username = username
-        self.password = password
-        
-        log.msg("register", nickname)
-        irc.IRCClient.register(self, nickname)
-
-        # defer
-        d = self._registering = defer.Deferred()
-
-        return d
-    
-    # irc_ERR_NICKNAMEINUSE
-    # alterCollidedNick
-    # irc_ERR_ERRONEUSNICKNAME
-
-    def irc_ERR_PASSWDMISMATCH (self, prefix, params) :
-        err = IRCError('ERR_PASSWDMISMATCH')
-        log.err(err)
-        self._registering.errback(err)
-
-    def irc_RPL_WELCOME (self, prefix, params) :
-        self.hostname = prefix
-        irc.IRCClient.irc_RPL_WELCOME(self, prefix, params)
-
-    def signedOn (self) :
-        log.msg("signedOn", self.nickname)
-        irc.IRCClient.signedOn(self)
-        
-        # defer
-        d = self._registering
-
-        if not d :
-            raise Exception("signedOn: not registering?")
-
-        self._registering = None
-
-        d.callback(self.nickname)
-    
-    ## Channels
-    def join (self, channel, key=None) :
-        """
-            Join the given channel.
-
-            Returns a deferred that callbacks with the IRCChannel once joined, or errbacks.
-        """
-        
-        irc.IRCClient.join(self, channel, key=key)
-
-        d = self._channels[normalize(channel)] = defer.Deferred()
-
-        return d
-
-    # ERR_CHANNELISFULL
-    # ERR_INVITEONLYCHAN
-    # ERR_BANNEDFROMCHAN
-    # ERR_BADCHANNELKEY
-    
-    def _close_channel (self, channel) :
-        """
-            Remove channel from our list of channels.
-
-            TODO: purge queued messages for channel?
-        """
-
-        del self._channels[normalize(channel)]
-
-    def left (self, channel) :
-        if normalize(channel) in self._channels :
-            if isinstance(self._channels[normalize(channel)], defer.Deferred) :
-                log.msg('IRCClient.left: part during join:', channel)
-
-            else :
-                log.msg('IRCClient.left: unexpected part:', channel)
-                self._close_channel(channel)
-
-            # XXX: assume this is: send PART, send JOIN, receive PART, receive JOIN
-            #self._close_channel(channel)
-        else :
-            log.msg("IRCClient.left: parted channel:", channel)
-
-    def kickedFrom (self, channel, kicker, message) :
-        log.msg('IRCClient.kicked', channel, kicker, message)
-        
-        self._close_channel(channel)
-
-    def joined (self, channel) :
-        """
-            Have joined given channel.
-        """
-        
-        lookup = normalize(channel)
-
-        d = self._channels[lookup]
-        channel = self._channels[lookup] = IRCChannel(self, channel)
-        d.callback(channel)
-
-    @defer.inlineCallbacks
-    def channel (self, channel, key=None) :
-        """
-            Defer a joined IRCChannel.
-        """
-
-        lookup = normalize(channel)
-        
-        log.msg('IRCClient.channel', lookup, channel)
-
-        if lookup not in self._channels :
-            channel = yield self.join(channel, key)
-        else :
-            # wait or get
-            yield self._channels[lookup]
-
-        channel = self._channels[lookup]
-
-        log.msg('IRCClient.channel', lookup, channel)
-        
-        defer.returnValue(channel)
-
-    ## 
-    def irc_ERR_CANNOTSENDTOCHAN (self, prefix, params) :
-        nick, channel, error = params
-
-        log.err(IRCError(channel, error))
-
-    ## Quit
-    def irc_ERROR (self, prefix, params) :
-        msg, = params
-        error = IRCError(None, msg)
-
-        log.err(error)
-
-        if self._registering :
-            self._registering.errback(error)
-            self._registering = None
-
-    def connectionLost (self, reason) :
-        irc.IRCClient.connectionLost(self, reason)
-        log.err(reason)
-
-        if self._registering :
-            self._registering.errback(reason)
-            self._registering = None
-        
-        # unregister channels
-        for channel in self._channels :
-            # errback Deferred or IRCChannel
-            self._channels[channel].errback(reason)
-        
-        self._channels = { }
-
-        # unregister client
-        self.factory.clientLost(self)
-    
-    ## Logging
-    def url (self, target=None) :
-        """
-            Format as URL.
-        """
-
-        if not self.transport : return 'IRC'
-        
-        # XXX: no isinstance() support
-        if interfaces.ITCPTransport.providedBy(self.transport) :
-            scheme = 'irc'
-        else :
-            # TODO: ssl?
-            scheme = None
-        
-        peer = self.transport.getPeer()
-
-        if peer.port == PORT :
-            peer = "{irc.hostname}".format(irc=self, peer=peer)
-        else :
-            peer = "{irc.hostname}:{peer.port}".format(irc=self, peer=peer)
-
-        if target :
-            path = str(target)
-        else :
-            path = None
-        
-        return ''.join(part for part in (
-            scheme, '://' if scheme else None,
-            self.nickname, '@' if self.nickname else None,
-            peer,
-            '/' if path else None, path
-        ) if part)
-
-    __str__ = url
-    logPrefix = url
-
-class IRCFactory (protocol.ClientFactory) :
-    """
-        Manage Clients and Targets
-    """
-
-    NICKNAME = 'irker'
-
-    def __init__ (self, nickname=NICKNAME, username=None) :
-        # default nickname
-        self.nickname = nickname
-        self.username = username
-
-        # (scheme, host, port, nick) -> IRCClient
-        self.clients = {}
-    
-    def buildProtocol (self, addr) :
-        return IRCClient(self)
-
-    def clientLost (self, client) :
-        """
-            Given IRCClient is no more.
-        """
-
-        log.msg("IRCFactory.clientLost", client)
-        
-        # remove from our clients
-        self.clients = dict((k, c) for k, c in self.clients.iteritems() if c != client)
-
-    @defer.inlineCallbacks
-    def connect (self, url) :
-        """
-            Defer a connected, registered Client for given URL.
-        """
-        
-        endpoint = url2endpoint(reactor, url)
-
-        log.msg('IRCFactory.connect', url, ':', endpoint)
-        
-        # connect
-        try :
-            client = yield endpoint.connect(self)
-        
-        except error.ConnectError as ex :
-            log.err(ex, ': '.join(str(x) for x in ('IRCFactory.connect', url, endpoint)))
-            raise
-        
-        else :
-            log.msg('IRCFactory.connect', url, ':', endpoint, ':', client)
-        
-        # register
-        try :
-            nickname = yield client.register(url.username or self.nickname, 
-                    username    = self.username,
-                    password    = url.password, 
-            )
-
-        except Exception as ex :
-            log.err("register", ex)
-            raise
-        
-        log.msg('IRCFactory.connect', url, ':', endpoint, ':', client, ':', nickname)
-
-        # okay!
-        defer.returnValue(client)
-
-    @defer.inlineCallbacks
-    def client (self, url) :
-        """
-            Return IRCClient for given URL.
-        """
-
-        lookup = (url.scheme, url.hostname, url.port, url.username)
-
-        if lookup not in self.clients :
-            # deferred for connect
-            connect = self.clients[lookup] = self.connect(url)
-            
-            try :
-                # wait on deferred, and then store IRCClient
-                self.clients[lookup] = yield connect
-
-            except Exception as ex :
-                # failed, remove the attempted connect
-                del self.clients[lookup]
-                raise
-
-        else :
-            # wait for result, if deferred
-            # XXX: this yields None, since the first inlineCallbacks yielding on the deferred returns None in its callback
-            yield self.clients[lookup]
-        
-        # connected client
-        client = self.clients.get(lookup)
-
-        if client :
-            log.msg('IRCFactory.client', url, ":", client)
-
-            defer.returnValue(client)
-        else :
-            log.msg('IRCFactory.client', url, ": client connect failed")
-            
-            # XXX: get failure from first yield's errback... except inlineCallbacks drops it and goes to callback with None <_<
-            raise Exception("Client connect failed")
-
-    @defer.inlineCallbacks
-    def target (self, url) :
-        """
-            Return IRCChannel for given URL.
-        """
-
-        client = yield self.client(url)
-        
-        channel = '#' + url.path.lstrip('/')
-        channel = yield client.channel(channel)
-        
-        log.msg('IRCFactory.target', url, ":", channel)
-
-        defer.returnValue(channel)
-
-    @defer.inlineCallbacks
-    def privmsg (self, url, *msg) :
-        """
-            Dispatch given messages to given target.
-        """
-
-        target = yield self.target(url)
-        
-        log.msg('IRCFactory.privmsg', url, ":", target, ':', *msg)
-
-        target.privmsg(*msg)