예제 #1
0
    def __init__(self, server, port, channels, nick, db, loop=None):
        irclib.SimpleIRCClient.__init__(self)
        
        # tornado ioloop
        if loop is None:
            loop = ioloop.IOLoop.instance()
        self.loop = loop
        self._last_connected = time.time()
        self._reconnect_interval = 30
        
        #IRC details
        self.server = server
        self.port = port
        self.channels = channels
        self.names = {}
        for channel in self.channels:
            self.names[channel] = set()
        
        self.nick = nick
        
        #DB details
        self.db = IRCDatabase( db )

        
        #Regexes
        self.nick_reg = re.compile("^" + nick + "[:,](?iu)")
        
        #Message Cache
        self.message_cache = []        #messages are stored here before getting pushed to the db
        
        #Disconnect Countdown
        self.disconnect_countdown = 5
        
        self._connect()
        
        self.last_ping = time.time()
        
        self.ping_callback = pc = ioloop.PeriodicCallback(self._no_ping, 60000, self.loop)
        pc.start()
예제 #2
0
class IRCLogger(irclib.SimpleIRCClient):
    
    def __init__(self, server, port, channels, nick, db, loop=None):
        irclib.SimpleIRCClient.__init__(self)
        
        # tornado ioloop
        if loop is None:
            loop = ioloop.IOLoop.instance()
        self.loop = loop
        self._last_connected = time.time()
        self._reconnect_interval = 30
        
        #IRC details
        self.server = server
        self.port = port
        self.channels = channels
        self.names = {}
        for channel in self.channels:
            self.names[channel] = set()
        
        self.nick = nick
        
        #DB details
        self.db = IRCDatabase( db )

        
        #Regexes
        self.nick_reg = re.compile("^" + nick + "[:,](?iu)")
        
        #Message Cache
        self.message_cache = []        #messages are stored here before getting pushed to the db
        
        #Disconnect Countdown
        self.disconnect_countdown = 5
        
        self._connect()
        
        self.last_ping = time.time()
        
        self.ping_callback = pc = ioloop.PeriodicCallback(self._no_ping, 60000, self.loop)
        pc.start()
        
        # failure = ioloop.PeriodicCallback(self._FAKE_DISCONNECT, 25000, self.loop)
        # failure.start()
        
    def _FAKE_DISCONNECT(self):
        """fake disconnect callback for debugging"""
        logging.critical("DISCONNECTING MYSELF")
        if self.connection.is_connected():
            self.connection.disconnect()
    
    def _check_connect(self):
        """Connect to a new server, possibly disconnecting from the current.

        The bot will skip to next server in the server_list each time
        jump_server is called.
        """
        raise RuntimeError("_check_connect should not be used!")
        logging.debug("checking connection")
        if self.connection.is_connected():
            return
    
    def _connect(self):
        logging.info("IRC:connecting to %s:%i %s as %s...", self.server, self.port, self.channels, self.nick)
        irclib.SimpleIRCClient.connect(self, self.server, self.port, self.nick)
        self._connection_fd = self.connection.socket.fileno()
        self.loop.add_handler(self._connection_fd, self._handle_message, self.loop.READ)
        self._last_connect = time.time()
    
    def _handle_message(self, fd, events):
        logging.debug("dispatching message")
        self.connection.process_data()
    
    def _no_ping(self):
        elapsed = time.time() - self.last_ping
        logging.debug("last ping: %is ago", elapsed)
        if elapsed >= 1200:
            logging.critical("No ping in %is: reconnecting", elapsed)
            self.loop.stop()

    def _dispatcher(self, c, e):
        """dispatch events"""
        etype = e.eventtype()
        logging.debug(u"dispatch: %s: %r : %r : %r", etype, e.target(), e.source(), e.arguments())
        if etype in ('topic', 'part', 'join', 'action', 'quit', 'nick', 'pubmsg'):
            try: 
                source = cast_unicode(e.source().split("!")[0])
            except IndexError:
                source = u''
            try:
                text = cast_unicode(e.arguments()[0])
            except IndexError:
                text = u''
            
            # update names lists
            if etype == 'join':
                channel = e.target()
                logging.info("%s joined %s", source, channel)
                self.names[channel].add(source)
            elif etype == 'part':
                channel = e.target()
                logging.info("%s parted %s", source, channel)
                self.names[channel].remove(source)
            
            # Prepare a message for the buffer
            message_dict = {"channel": e.target() or u'',
                            "name": source,
                            "message": text,
                            "type": etype,
                            "time": datetime.datetime.utcnow() } 
                            
            if etype == "nick":
                message_dict["message"] = e.target()
                before = source
                after = e.target()
                
                found = []
                for channel, nameset in self.names.items():
                    if before in nameset:
                        nameset.remove(before)
                        nameset.add(after)
                        logging.info("%s renamed to %s in %s", before, after, channel)
                        md = dict(message_dict)
                        md['channel'] = channel
                        self.message_cache.append( md )
            elif etype == "quit":
                name = source
                logging.info("%s quit", name)
                for channel, nameset in self.names.items():
                    if name in nameset:
                        nameset.remove(name)
                        md = dict(message_dict)
                        md['channel'] = channel
                        self.message_cache.append( md )
            else:
                # Most of the events are pushed to the buffer. 
                self.message_cache.append( message_dict )
        
        m = "on_" + etype
        if hasattr(self, m):
            getattr(self, m)(c, e)

    def on_nicknameinuse(self, c, e):
        logging.error("nick in use: %s", c.get_nickname())
        
        c.nick(c.get_nickname() + "_")

    def on_welcome(self, connection, event):
        logging.info("welcome")
        for channel in self.channels:
            connection.join(channel)

    def on_disconnect(self, connection, event):
        logging.warn("disconnect")
        self.on_ping(connection, event)
        self.loop.remove_handler(self._connection_fd)
        logging.warn("connection lost, triggering reconnect")
        
        if (time.time() - self._last_connect) < 600:
            # short-lived connection, throttle reconnect
            interval = self._reconnect_interval
            if interval > 600:
                # tried slowing down to 10 minutes, still failed:
                logging.critical("reconnecting doesn't seem to be working, giving up")
                self.loop.stop()
                return
            else:
                # exponentially throttle reconnects
                self._reconnect_interval = interval * 2
            logging.error("last reconnect seems to have failed, next try in %is", interval)
        else:
            # last connect seemed to go fine, start with 30s check interval
            interval = self._reconnect_interval = interval * 2
            logging.info("last connect was okay, back to %is check", interval)
        self.loop.add_timeout(time.time() + interval, self._connect)
    
    def on_namreply(self, connection, event):
        owner = cast_unicode(event.arguments()[0])
        channel = cast_unicode(event.arguments()[1])
        names = cast_unicode(event.arguments()[-1]).split()
        logging.info("got names for %s: %s", channel, names)
        nameset = self.names[channel]
        for name in names:
            nameset.add(name.lstrip(owner))
        

    def on_ping(self, connection, event):
        logging.info("ping")
        logging.debug("Current channel members: %s", self.names)
        self.last_ping = time.time()
        self.save_messages()
    
    def save_messages(self):
        if not self.message_cache:
            logging.debug("no messages to save")
            return
        else:
            logging.info("saving %i messages" % len(self.message_cache))
        
        try:
            for message in self.message_cache:
                self.db.insert_line(message["channel"], message["name"], message["time"], message["message"], message["type"] )
            
            self.db.commit()
            if self.disconnect_countdown < 5:
                self.disconnect_countdown = self.disconnect_countdown + 1
            
            # clear the cache
            self.message_cache = []
                
        except Exception:
            logging.error("Couldn't commit to db: %s", self.db.fname, exc_info=True)
            if self.disconnect_countdown <= 0:
                self.loop.stop()
            # connection.privmsg(self.channel, "Database connection lost! " + str(self.disconnect_countdown) + " retries until I give up entirely!" )
            self.disconnect_countdown = self.disconnect_countdown - 1
            

    def on_pubmsg(self, connection, event):
        try:
            text = cast_unicode(event.arguments()[0])
        except IndexError:
            text = u''
        channel = cast_unicode(event.target() or '')
        
        logging.info("pubmsg: %s", text)

        # If you talk to the bot, this is how he responds.
        if self.nick_reg.search(text):
            logging.debug("bloop")
            if text.split(" ")[1] and text.split(" ")[1] == "quit":
                connection.privmsg(channel, "Goodbye.")
                self.on_ping( connection, event )
                sys.exit( 0 ) 
                
            if text.split(" ")[1] and text.split(" ")[1] == "ping":
                connection.privmsg(channel, "Pong.")
                self.on_ping(connection, event)
                return
    
    def start_saving(self, interval=5000):
        pc = ioloop.PeriodicCallback(self.save_messages, interval, self.loop)
        pc.start()
    
    def start(self):
        self.start_saving()
        self.loop.start()