Esempio n. 1
0
class Campy(object):
    def __init__(self):
        self.client = Campfire(settings.CAMPFIRE_SUBDOMAIN,
                                settings.CAMPFIRE_API_KEY, ssl=True)

        self.rooms = []
        self.since_message_id = None

        self.plugins = []
        for plugin in settings.REGISTERED_PLUGINS:
            path = plugin.split('.')
            klass = path.pop()
            plugin_obj = getattr(__import__('.'.join(path), globals(), locals(), [klass], -1), klass)
            self.plugins.append(plugin_obj())

        for room in settings.CAMPFIRE_ROOMS:
            print "Joining %s" % room
            room = self.client.find_room_by_name(room)
            if room:
                self.rooms.append(room)
                room.join()


    def listen(self):
        def callback(message):
            for plugin in self.plugins:
                try:
                    speaker = self.client.user(message['user_id'])
                    if not message['body']:
                        return

                    if re.match('%s: help' % settings.CAMPFIRE_BOT_NAME, message['body']):
                        try:
                            plugin.send_help(self.client, self.client.room(message['room_id']),
                                        message, speaker)
                        except NotImplementedError:
                            pass
                    else:
                        plugin.handle_message(self.client, self.client.room(message['room_id']),
                                          message, speaker)
                except HTTPNotFoundException:
                    pass

        def errback(message):
            print message

        # Setup all rooms first
        for room in self.rooms:
            room.listen(callback, errback, False)

        # Then start listening to those rooms
        reactor.run()

    def die(self):
        for room in self.rooms:
            if settings.SAY_GOODBYE:
                room.speak("Goodbye!")
            if settings.LEAVE_ON_EXIT:
                room.leave()
Esempio n. 2
0
class Campy(object):
    def __init__(self, **kwargs):
        # This is the directory we were launched in, before we move to the 
        # Campy home directory
        self.launchdir = os.getcwd()
        # In some cases, it might be useful to provide the subdomain and
        # an API key in the initializer (perhaps passed in as a command-
        # line argument?)
        self.subdomain = kwargs.get('subdomain', None)
        self.apiKey    = kwargs.get('api_key', None)
        # When we get a message from Campfire, we're supplied with the room_id
        # as one of the parameters of the message. As such, we'll have a room
        # dictionary so we can quickly look up the appropriate room object
        self.rooms = {}
        # This is a list of all our plugins, where each is an instance of the 
        # type campy.CampyPlugin, and the keys are the regular expressions they
        # are meant to match.
        from campy.plugins import sh, say, config, alias
        self.plugins = {
            'sh'    : sh.ShPlugin(self),
            'say'   : say.SayPlugin(self),
            'config': config.ConfigPlugin(self),
            'alias' : alias.AliasPlugin(self)
        }
        # This is the name that the campy bot will respond to. It can be 
        # provided in the constructor, or it can be provided in the settings.
        # The value provided in the constructor overrides what's specified in
        # the settings, and it defaults to 'campy'
        self.name = kwargs.get('name', None)
        # This is the regular expression that matches our bot's name, and the
        # corresponding message to parse
        self.nameRE = None
        
        # This is our actual campfire client. It will be made when we read in
        # our settings. However, for now, it is None. That value is used as
        # a semaphore indicating whether or not this is the first read-in or
        # not.
        self.client = None
        
        self.goodbye     = 'Goodbye!'
        self.leaveOnExit = True
        
        # Try to set up our home directory
        self.home   = kwargs.get('home', os.path.expanduser('~/.campy'))
        if not os.path.exists(self.home):
            try:
                os.mkdir(self.home)
            except Exception as e:
                log.exception('Cannot make directory %s' % self.home)
        
        # Set the configuration files
        self.configs = [
            os.path.join(self.home, 'campy.conf'),
            os.path.join(self.launchdir, 'campy.conf')
        ]
        self.read()

    def findPlugin(self, n):
        '''Try to locate the module with this particular name'''
        try:
            log.debug('Trying to import %s' % n)
            plugins = []
            # If we've already loaded this particular module...
            try:
                import campy
                m = __import__('campy.plugins.%s' % n)
                m = getattr(campy, 'plugins')
                m = getattr(m, n)
            except ImportError:
                m = __import__(n)
            for name, klass in inspect.getmembers(m):
                try:
                    if issubclass(klass, CampyPlugin):
                        plugins.append(klass)
                except:
                    continue
            return plugins
        except:
            log.exception('Could not find plugin %s' % n)
            return []
    
    def updatePath(self):
        for path in os.listdir(self.home):
            if not path.startswith('.'):
                p = os.path.join(self.home, path)
                if p not in sys.path:
                    log.debug('Appending %s to path' % p)
                    sys.path.append(p)
    
    def reloadPlugin(self, n):
        '''Try to reload a given module'''
        try:
            reload(n)
        except Exception:
            log.exception('Could not reload %s' % n)
    
    def update(self, update, destination=None):
        if not destination:
            destination = self.data
        for key, value in update.items():
            if key in destination and isinstance(destination[key], dict):
                self.update(value, destination[key])
            else:
                destination[key] = value
        return destination

    def reload(self, **kwargs):
        '''Given a new set of configurations, make sure we're up to date'''
        self.updatePath()
        log.info(repr(kwargs))
        subdomain = kwargs.get('subdomain', self.subdomain)
        apiKey    = kwargs.get('api_key', self.apiKey)
        if not subdomain and not apiKey:
            log.critical('Need a subdomain and API key')
            exit(1)
        self.client = Campfire(subdomain, apiKey)
        
        # Now register the bot's name
        self.name   = kwargs.get('name', self.name) or 'campy'
        self.nameRE = re.compile(r'\s*%s\s+(.+)\s*$' % re.escape(self.name), re.I)
        
        # Now join all the appropriate rooms
        for room in kwargs.get('rooms', []):
            if not self.joinRoom(room):
                log.warn('Could not find room %s' % room)
            else:
                log.debug('Joined %s' % room)
    
    def read(self, overrides={}):
        '''Re-read the settings, and do all the appropriate imports'''
        # We're going to build up a single dictionary, making sure to merge
        # and override all options in subsequent config files. Then, and
        # only then will we start to go through it.
        log.debug('Reading configuration files...')
        self.data = {}
        for fname in self.configs:
            try:
                with file(fname) as f:
                    self.data.update(yaml.load(f))
            except Exception as e:
                log.error('Could not read %s => %s' % (fname, repr(e)))
        self.update(overrides)
        # There has to at least be a campy section
        if 'campy' not in self.data:
            log.critical('No campy configuration found in config files!')
            exit(1)
        # Now go through the remaining sections
        for section, values in self.data.items():
            if section == 'campy':
                if not values:
                    values = {}
                self.reload(**values)
                continue
            if section == 'logger':
                import logging.config
                logging.config.dictConfig(values)
                continue
            # If it wasn't the campy section, then we should try to
            # import the module named in that section, and then to
            # instantiate each of the plugins that inherit from 
            # plugins.CampyPlugin
            try:
                for m in self.findPlugin(section):
                    # Alright! We found a plugin. Now, let's see
                    # if the plugin has been instantiated, or if
                    # it hasn't yet been encountered
                    plugin = self.plugins.get(m.shortname)
                    if plugin:
                        if not values:
                            values = {}
                        plugin.reload(**values)
                    else:
                        log.debug('Storing plugin at %s' % m.shortname)
                        values = values or {}
                        self.plugins[m.shortname] = m(self, **values)
            except ImportError:
                log.exception('Unable to import module %s' % section)
        log.debug('Read configuration files...')
    
    def save(self, filename=None):
        # Try to save the current configuration to a file.
        try:
            # To be safe when writing, the best thing to do is to first
            # save a temporary copy, and then if successful, replace the
            # original with the temporary
            path = filename or os.path.join(self.home, 'campy.conf')
            with file(path + '.bak', 'w+') as f:
                f.write(yaml.dump(self.data))
            
            # And now move the new file to replace the old file
            os.rename(path + '.bak', path)
            log.debug('Wrote to %s' % path)
            return 'Wrote configuration to %s' % path
        except Exception as e:
            log.exception('Campy save exception')
            return 'Campy save exception => %s' % repr(e)
    
    def callback(self, obj):
        '''Go through all the plugins and give it the appropriate message'''
        # The object provided looks something like this:
        #   'body'       : Text we heard
        #   'user_id'    : Who said it
        #   'created_at' : When it happened
        #   'room_id'    : The room where it was said
        #   'starred'    : A string representation ('true' or 'false')
        #   'type'       : Message type (e.g. 'TextMessage')
        #   'id'         : Message ID
        
        match = self.nameRE.match(obj.get('body', ''))
        if match:
            message = match.group(1)
            obj['body'] = message
        else:
            log.debug('Message "%s" did not match "%s "' % (obj.get('body', ''), self.name))
            return
        
        # The room that received the message:
        room = self.rooms.get(obj.get('room_id', ''), None)
        if room:
            log.info('I just heard : %s' % repr(obj))
        else:
            log.warn('I do not know about room %s' % obj.get('room_id', ''))
            return
        
        user = self.client.user(obj['user_id'])
        self.handle_message(self.client, room, obj, user)
    
    def handle_message(self, campfire, room, message, speaker):
        # Handle the join room command
        match = re.match(r'\s*join\s+(.+)\s*$', message['body'])
        if match:
            room.speak(self.joinRoom(match.group(1).strip()))
            return
        
        # Handle the leave room command
        match = re.match(r'\s*leave\s+(.+)\s*$', message['body'])
        if match:
            room.speak(self.leaveRoom(match.group(1).strip()))
            return

        # Handle the reload command
        match = re.match(r'\s*reload\s*$', message['body'])
        if match:
            room.speak('Reloading...')
            self.reload()
            return
        
        # See if it's a built-in command
        if re.match(r'\s*help\s*$', message['body']):
            self.handle_help(campfire, room, message, speaker)
            return
        
        # Handle all help requests...
        match = re.match(r'\s*help\s*(.+?)\s*$', message['body'])
        if match:
            name = match.group(1)
            plugin = self.plugins.get(name, None)
            if plugin:
                plugin.send_help(self.client, room, message, speaker)
                return
            # Alright, if we've made it here, nothing had help for this command
            room.speak('No help available for %s' % match.group(1))
            return
        
        # Otherwise, try to find the plugin that they were trying to talk to
        match = re.match(r'\s*(\S+)(\s*.*$|$)', message['body'])
        if match:
            name = match.group(1)
            plugin = self.plugins.get(name, None)
            if plugin:
                try:
                    plugin.handle_message(self.client, room, message, speaker)
                    return
                except Exception as e:
                    room.paste('Exception in %s => %s' % (name, repr(e)))
                    log.exception('Exception in %s' % name)
            else:
                room.speak('No plugin responds to %s' % message['body'])
        else:
            log.warn('No match.')
    
    def handle_help(self, campfire, room, message, speaker):
        room.paste('''Give this campfire bot commands by saying "campy <command>".
To find out more about a command, type "campy help <command>". The currently-loaded
plugins are: %s''' % ', '.join(self.plugins.keys()))
    
    def errback(self, failure):
        try:
            failure.raiseException()
        except:
            log.exception('Errback')
    
    def joinRoom(self, name):
        room = self.client.find_room_by_name(name)
        if room:
            if room.id in self.rooms:
                log.debug('Already in room %s' % name)
                return 'Already in room %s' % name
            else:
                self.rooms[room.id] = room
                room.join()
                room.listen(self.callback, self.errback, start_reactor=False)
                log.debug('Joined room %s' % name)
                return 'Joined room %s' % name
        else:
            log.debug('Could not find room %s' % name)
            return 'Couldn\'t find room %s' % name
    
    def leaveRoom(self, name):
        room = self.client.find_room_by_name(name)
        if room:
            if len(self.rooms) > 1:
                if room.id in self.rooms:
                    del self.rooms[room.id]
                    return 'Left room %s' % name
                else:
                    return 'I\'m not in room %s' % name
            else:
                return '%s is the last room I\'m in, and I\'m not leaving!' % name
        else:
            return 'Couldn\'t find room %s' % name
    
    def start(self):
        log.info('Starting campy...')
        reactor.run()
    
    def stop(self):
        for room in self.rooms.values():
            room.speak(self.goodbye)
            if self.leaveOnExit:
                room.leave()
Esempio n. 3
0
class Campy(object):
    def __init__(self, **kwargs):
        # This is the directory we were launched in, before we move to the
        # Campy home directory
        self.launchdir = os.getcwd()
        # In some cases, it might be useful to provide the subdomain and
        # an API key in the initializer (perhaps passed in as a command-
        # line argument?)
        self.subdomain = kwargs.get('subdomain', None)
        self.apiKey = kwargs.get('api_key', None)
        # When we get a message from Campfire, we're supplied with the room_id
        # as one of the parameters of the message. As such, we'll have a room
        # dictionary so we can quickly look up the appropriate room object
        self.rooms = {}
        # This is a list of all our plugins, where each is an instance of the
        # type campy.CampyPlugin, and the keys are the regular expressions they
        # are meant to match.
        from campy.plugins import sh, say, config, alias
        self.plugins = {
            'sh': sh.ShPlugin(self),
            'say': say.SayPlugin(self),
            'config': config.ConfigPlugin(self),
            'alias': alias.AliasPlugin(self)
        }
        # This is the name that the campy bot will respond to. It can be
        # provided in the constructor, or it can be provided in the settings.
        # The value provided in the constructor overrides what's specified in
        # the settings, and it defaults to 'campy'
        self.name = kwargs.get('name', None)
        # This is the regular expression that matches our bot's name, and the
        # corresponding message to parse
        self.nameRE = None

        # This is our actual campfire client. It will be made when we read in
        # our settings. However, for now, it is None. That value is used as
        # a semaphore indicating whether or not this is the first read-in or
        # not.
        self.client = None

        self.goodbye = 'Goodbye!'
        self.leaveOnExit = True

        # Try to set up our home directory
        self.home = kwargs.get('home', os.path.expanduser('~/.campy'))
        if not os.path.exists(self.home):
            try:
                os.mkdir(self.home)
            except Exception as e:
                log.exception('Cannot make directory %s' % self.home)

        # Set the configuration files
        self.configs = [
            os.path.join(self.home, 'campy.conf'),
            os.path.join(self.launchdir, 'campy.conf')
        ]
        self.read()

    def findPlugin(self, n):
        '''Try to locate the module with this particular name'''
        try:
            log.debug('Trying to import %s' % n)
            plugins = []
            # If we've already loaded this particular module...
            try:
                import campy
                m = __import__('campy.plugins.%s' % n)
                m = getattr(campy, 'plugins')
                m = getattr(m, n)
            except ImportError:
                m = __import__(n)
            for name, klass in inspect.getmembers(m):
                try:
                    if issubclass(klass, CampyPlugin):
                        plugins.append(klass)
                except:
                    continue
            return plugins
        except:
            log.exception('Could not find plugin %s' % n)
            return []

    def updatePath(self):
        for path in os.listdir(self.home):
            if not path.startswith('.'):
                p = os.path.join(self.home, path)
                if p not in sys.path:
                    log.debug('Appending %s to path' % p)
                    sys.path.append(p)

    def reloadPlugin(self, n):
        '''Try to reload a given module'''
        try:
            reload(n)
        except Exception:
            log.exception('Could not reload %s' % n)

    def update(self, update, destination=None):
        if not destination:
            destination = self.data
        for key, value in update.items():
            if key in destination and isinstance(destination[key], dict):
                self.update(value, destination[key])
            else:
                destination[key] = value
        return destination

    def reload(self, **kwargs):
        '''Given a new set of configurations, make sure we're up to date'''
        self.updatePath()
        log.info(repr(kwargs))
        subdomain = kwargs.get('subdomain', self.subdomain)
        apiKey = kwargs.get('api_key', self.apiKey)
        if not subdomain and not apiKey:
            log.critical('Need a subdomain and API key')
            exit(1)
        self.client = Campfire(subdomain, apiKey)

        # Now register the bot's name
        self.name = kwargs.get('name', self.name) or 'campy'
        self.nameRE = re.compile(r'\s*%s\s+(.+)\s*$' % re.escape(self.name),
                                 re.I)

        # Now join all the appropriate rooms
        for room in kwargs.get('rooms', []):
            if not self.joinRoom(room):
                log.warn('Could not find room %s' % room)
            else:
                log.debug('Joined %s' % room)

    def read(self, overrides={}):
        '''Re-read the settings, and do all the appropriate imports'''
        # We're going to build up a single dictionary, making sure to merge
        # and override all options in subsequent config files. Then, and
        # only then will we start to go through it.
        log.debug('Reading configuration files...')
        self.data = {}
        for fname in self.configs:
            try:
                with file(fname) as f:
                    self.data.update(yaml.load(f))
            except Exception as e:
                log.error('Could not read %s => %s' % (fname, repr(e)))
        self.update(overrides)
        # There has to at least be a campy section
        if 'campy' not in self.data:
            log.critical('No campy configuration found in config files!')
            exit(1)
        # Now go through the remaining sections
        for section, values in self.data.items():
            if section == 'campy':
                if not values:
                    values = {}
                self.reload(**values)
                continue
            if section == 'logger':
                import logging.config
                logging.config.dictConfig(values)
                continue
            # If it wasn't the campy section, then we should try to
            # import the module named in that section, and then to
            # instantiate each of the plugins that inherit from
            # plugins.CampyPlugin
            try:
                for m in self.findPlugin(section):
                    # Alright! We found a plugin. Now, let's see
                    # if the plugin has been instantiated, or if
                    # it hasn't yet been encountered
                    plugin = self.plugins.get(m.shortname)
                    if plugin:
                        if not values:
                            values = {}
                        plugin.reload(**values)
                    else:
                        log.debug('Storing plugin at %s' % m.shortname)
                        values = values or {}
                        self.plugins[m.shortname] = m(self, **values)
            except ImportError:
                log.exception('Unable to import module %s' % section)
        log.debug('Read configuration files...')

    def save(self, filename=None):
        # Try to save the current configuration to a file.
        try:
            # To be safe when writing, the best thing to do is to first
            # save a temporary copy, and then if successful, replace the
            # original with the temporary
            path = filename or os.path.join(self.home, 'campy.conf')
            with file(path + '.bak', 'w+') as f:
                f.write(yaml.dump(self.data))

            # And now move the new file to replace the old file
            os.rename(path + '.bak', path)
            log.debug('Wrote to %s' % path)
            return 'Wrote configuration to %s' % path
        except Exception as e:
            log.exception('Campy save exception')
            return 'Campy save exception => %s' % repr(e)

    def callback(self, obj):
        '''Go through all the plugins and give it the appropriate message'''
        # The object provided looks something like this:
        #   'body'       : Text we heard
        #   'user_id'    : Who said it
        #   'created_at' : When it happened
        #   'room_id'    : The room where it was said
        #   'starred'    : A string representation ('true' or 'false')
        #   'type'       : Message type (e.g. 'TextMessage')
        #   'id'         : Message ID

        match = self.nameRE.match(obj.get('body', ''))
        if match:
            message = match.group(1)
            obj['body'] = message
        else:
            log.debug('Message "%s" did not match "%s "' %
                      (obj.get('body', ''), self.name))
            return

        # The room that received the message:
        room = self.rooms.get(obj.get('room_id', ''), None)
        if room:
            log.info('I just heard : %s' % repr(obj))
        else:
            log.warn('I do not know about room %s' % obj.get('room_id', ''))
            return

        user = self.client.user(obj['user_id'])
        self.handle_message(self.client, room, obj, user)

    def handle_message(self, campfire, room, message, speaker):
        # Handle the join room command
        match = re.match(r'\s*join\s+(.+)\s*$', message['body'])
        if match:
            room.speak(self.joinRoom(match.group(1).strip()))
            return

        # Handle the leave room command
        match = re.match(r'\s*leave\s+(.+)\s*$', message['body'])
        if match:
            room.speak(self.leaveRoom(match.group(1).strip()))
            return

        # Handle the reload command
        match = re.match(r'\s*reload\s*$', message['body'])
        if match:
            room.speak('Reloading...')
            self.reload()
            return

        # See if it's a built-in command
        if re.match(r'\s*help\s*$', message['body']):
            self.handle_help(campfire, room, message, speaker)
            return

        # Handle all help requests...
        match = re.match(r'\s*help\s*(.+?)\s*$', message['body'])
        if match:
            name = match.group(1)
            plugin = self.plugins.get(name, None)
            if plugin:
                plugin.send_help(self.client, room, message, speaker)
                return
            # Alright, if we've made it here, nothing had help for this command
            room.speak('No help available for %s' % match.group(1))
            return

        # Otherwise, try to find the plugin that they were trying to talk to
        match = re.match(r'\s*(\S+)(\s*.*$|$)', message['body'])
        if match:
            name = match.group(1)
            plugin = self.plugins.get(name, None)
            if plugin:
                try:
                    plugin.handle_message(self.client, room, message, speaker)
                    return
                except Exception as e:
                    room.paste('Exception in %s => %s' % (name, repr(e)))
                    log.exception('Exception in %s' % name)
            else:
                room.speak('No plugin responds to %s' % message['body'])
        else:
            log.warn('No match.')

    def handle_help(self, campfire, room, message, speaker):
        room.paste(
            '''Give this campfire bot commands by saying "campy <command>".
To find out more about a command, type "campy help <command>". The currently-loaded
plugins are: %s''' % ', '.join(self.plugins.keys()))

    def errback(self, failure):
        try:
            failure.raiseException()
        except:
            log.exception('Errback')

    def joinRoom(self, name):
        room = self.client.find_room_by_name(name)
        if room:
            if room.id in self.rooms:
                log.debug('Already in room %s' % name)
                return 'Already in room %s' % name
            else:
                self.rooms[room.id] = room
                room.join()
                room.listen(self.callback, self.errback, start_reactor=False)
                log.debug('Joined room %s' % name)
                return 'Joined room %s' % name
        else:
            log.debug('Could not find room %s' % name)
            return 'Couldn\'t find room %s' % name

    def leaveRoom(self, name):
        room = self.client.find_room_by_name(name)
        if room:
            if len(self.rooms) > 1:
                if room.id in self.rooms:
                    del self.rooms[room.id]
                    return 'Left room %s' % name
                else:
                    return 'I\'m not in room %s' % name
            else:
                return '%s is the last room I\'m in, and I\'m not leaving!' % name
        else:
            return 'Couldn\'t find room %s' % name

    def start(self):
        log.info('Starting campy...')
        reactor.run()

    def stop(self):
        for room in self.rooms.values():
            room.speak(self.goodbye)
            if self.leaveOnExit:
                room.leave()
Esempio n. 4
0
class Firebot(Chatbot):

    def __init__(self, name, commands, auth_token, subdomain, room_name, log_file='/var/log/chatbot.log', log_level=logging.INFO):
        super(Firebot, self).__init__(name, commands, log_file, log_level)
        self.auth_token = auth_token
        self.subdomain = subdomain
        self.room_name = room_name
        self.room = None
        self.users = {}
        
        reactor.addSystemEventTrigger('before', 'shutdown', self.disconnect)
        self.shutting_down = False

    def speak(self, user, message):
        if not self.room:
            self.logger.error("Must have a room before I can speak!")
            return

        if "\n" in message or "\r" in message:
            self.room.paste(message)
        else:
            self.room.speak(message)
    
    def sound(self, user, message):
        if not self.room:
            self.logger.error("Must have a room before I can make sounds!")
            return

        self.room.sound(message)

    def connect(self):
        self.campfire = Campfire(self.subdomain, self.auth_token)
        self.room = self.campfire.find_room_by_name(self.room_name)
        self.room.join()
        self.username = self.campfire.me()['name']

        def callback(message):
            text = message['body']
            user_id = message['user_id']
            user = self.users.get(user_id)

            if user_id:
                if not user:
                    user = self.campfire.user(user_id)['user']['name']
                    self.users[user_id] = user
    
                self.perform_action(user, text)

        def err_callback(exc):
            self.error(exc)

        self.room.join()
        self.room.listen(callback, err_callback, start_reactor=False)
        application = service.Application("firebot")
        return application

    def error(self, exc):
        self.room.leave()
        self.logger.error(traceback.format_exc())
        if not self.shutting_down:
            self.logger.info("Was disconnected, trying again in 10 seconds.")
            time.sleep(10)
            self.connect()

    def disconnect(self):
        self.logger.info("Disconnect was called. The next error you see is simply Twisted shutting down.")
        self.shutting_down = True
        self.room.leave()
        reactor.stop()