def test_handle_webhook(self): # Create an initialized plugin so its listeners are registered driver = Driver() plugin = WebHookExample().initialize(driver, Settings()) # Construct a handler with it handler = EventHandler(driver, Settings(), plugins=[plugin]) # Mock the call_function of the plugin so we can make some asserts async def mock_call_function(function, event, groups): # This is the regexp that we're trying to trigger assert function.matcher.pattern == "ping" assert event.text == "hello!" assert groups == [] with mock.patch.object( plugin, "call_function", wraps=mock_call_function ) as mocked: asyncio.run( handler._handle_webhook( WebHookEvent( body={"text": "hello!"}, request_id="request_id", webhook_id="ping", ), ) ) # Assert the function was called, so we know the asserts succeeded. mocked.assert_called_once()
def test_handle_post(self): # Create an initialized plugin so its listeners are registered driver = Driver() plugin = ExamplePlugin().initialize(driver) # Construct a handler with it handler = EventHandler(driver, Settings(), plugins=[plugin]) # Mock the call_function of the plugin so we can make some asserts async def mock_call_function(function, message, groups): # This is the regexp that we're trying to trigger assert function.matcher.pattern == "sleep ([0-9]+)" assert message.text == "sleep 5" # username should be stripped off assert groups == ["5"] # arguments should be matched and passed explicitly with mock.patch.object( plugin, "call_function", wraps=mock_call_function ) as mocked: # Transform the default message into a raw post event so we can pass it new_body = create_message(text="@my_username sleep 5").body.copy() new_body["data"]["post"] = json.dumps(new_body["data"]["post"]) new_body["data"]["mentions"] = json.dumps(new_body["data"]["mentions"]) asyncio.run(handler._handle_post(new_body)) # Assert the function was called, so we know the asserts succeeded. mocked.assert_called_once()
def __init__( self, settings: Optional[Settings] = None, plugins: Optional[Sequence[Plugin]] = None, ): if plugins is None: plugins = [ExamplePlugin(), WebHookExample()] # Use default settings if none were specified. self.settings = settings or Settings() logging.basicConfig( **{ "format": "[%(asctime)s] %(message)s", "datefmt": "%m/%d/%Y %H:%M:%S", "level": logging.DEBUG if self.settings.DEBUG else logging.INFO, "stream": sys.stdout, }) self.driver = Driver({ "url": self.settings.MATTERMOST_URL, "port": self.settings.MATTERMOST_PORT, "token": self.settings.BOT_TOKEN, "scheme": self.settings.SCHEME, "verify": self.settings.SSL_VERIFY, }) self.driver.login() self.plugins = self._initialize_plugins(plugins) self.event_handler = EventHandler(self.driver, settings=self.settings, plugins=self.plugins) self.webhook_server = None if self.settings.WEBHOOK_HOST_ENABLED: self._initialize_webhook_server()
def test_handle_event(self, handle_post): handler = EventHandler(Driver(), Settings(), plugins=[]) # This event should trigger _handle_post asyncio.run(handler._handle_event(json.dumps(create_message().body))) # This event should not asyncio.run(handler._handle_event(json.dumps({"event": "some_other_event"}))) handle_post.assert_called_once_with(create_message().body)
def __init__( self, settings: Optional[Settings] = None, plugins: Optional[Sequence[Plugin]] = None, ): if plugins is None: plugins = [ExamplePlugin(), WebHookExample()] # Use default settings if none were specified. self.settings = settings or Settings() logging.basicConfig( **{ "format": self.settings.LOG_FORMAT, "datefmt": "%m/%d/%Y %H:%M:%S", "level": logging.DEBUG if self.settings.DEBUG else logging.INFO, "filename": self.settings.LOG_FILE, "filemode": "w", }) # define and add a Handler which writes log messages to the sys.stdout self.console = logging.StreamHandler(stream=sys.stdout) self.console.setFormatter(logging.Formatter(self.settings.LOG_FORMAT)) logging.getLogger("").addHandler(self.console) self.driver = Driver({ "url": self.settings.MATTERMOST_URL, "port": self.settings.MATTERMOST_PORT, "token": self.settings.BOT_TOKEN, "scheme": self.settings.SCHEME, "verify": self.settings.SSL_VERIFY, "keepalive": True, "connect_kw_args": { "ping_interval": None }, }) self.driver.login() self.plugins = self._initialize_plugins(plugins) self.event_handler = EventHandler(self.driver, settings=self.settings, plugins=self.plugins) self.webhook_server = None if self.settings.WEBHOOK_HOST_ENABLED: self._initialize_webhook_server() self.running = False
def __init__( self, settings: Optional[Settings] = None, plugins: Optional[Sequence[Plugin]] = None, enable_logging: bool = True, ): if plugins is None: plugins = [ExamplePlugin(), WebHookExample()] # Use default settings if none were specified. self.settings = settings or Settings() if enable_logging: self._register_logger() else: self.console = None self.driver = Driver({ "url": self.settings.MATTERMOST_URL, "port": self.settings.MATTERMOST_PORT, "token": self.settings.BOT_TOKEN, "scheme": self.settings.SCHEME, "verify": self.settings.SSL_VERIFY, "basepath": self.settings.MATTERMOST_API_PATH, "keepalive": True, "connect_kw_args": { "ping_interval": None }, }) self.driver.login() self.plugins = self._initialize_plugins(plugins) self.event_handler = EventHandler(self.driver, settings=self.settings, plugins=self.plugins) self.webhook_server = None if self.settings.WEBHOOK_HOST_ENABLED: self._initialize_webhook_server() self.running = False
def test_should_ignore(self): handler = EventHandler( Driver(), Settings(IGNORE_USERS=["ignore_me"]), plugins=[] ) # We shouldn't ignore a message from betty, since she is not listed assert not handler._should_ignore(create_message(sender_name="betty")) assert handler._should_ignore(create_message(sender_name="ignore_me")) # We ignore our own messages by default assert handler._should_ignore(create_message(sender_name="my_username")) # But shouldn't do so if this is explicitly requested handler = EventHandler( Driver(), Settings(IGNORE_USERS=["ignore_me"]), plugins=[], ignore_own_messages=False, ) assert not handler._should_ignore(create_message(sender_name="my_username"))
def test_init(self): handler = EventHandler( Driver(), Settings(), plugins=[ExamplePlugin(), WebHookExample()] ) # Test the name matcher regexp assert handler._name_matcher.match("@my_username are you there?") assert not handler._name_matcher.match("@other_username are you there?") # Test that all listeners from the individual plugins are now registered on # the handler for plugin in handler.plugins: for pattern, listener in plugin.message_listeners.items(): assert listener in handler.message_listeners[pattern] for pattern, listener in plugin.webhook_listeners.items(): assert listener in handler.webhook_listeners[pattern] # And vice versa, check that any listeners on the handler come from the # registered plugins for pattern, listeners in handler.message_listeners.items(): for listener in listeners: assert any( [ pattern in plugin.message_listeners and listener in plugin.message_listeners[pattern] for plugin in handler.plugins ] ) for pattern, listeners in handler.webhook_listeners.items(): for listener in listeners: assert any( [ pattern in plugin.webhook_listeners and listener in plugin.webhook_listeners[pattern] for plugin in handler.plugins ] )
class Bot: """Base chatbot class. Can be either subclassed for custom functionality, or used as-is with custom plugins and settings. To start the bot, simply call bot.run(). """ def __init__( self, settings: Optional[Settings] = None, plugins: Optional[Sequence[Plugin]] = None, enable_logging: bool = True, ): if plugins is None: plugins = [ExamplePlugin(), WebHookExample()] # Use default settings if none were specified. self.settings = settings or Settings() if enable_logging: self._register_logger() else: self.console = None self.driver = Driver({ "url": self.settings.MATTERMOST_URL, "port": self.settings.MATTERMOST_PORT, "token": self.settings.BOT_TOKEN, "scheme": self.settings.SCHEME, "verify": self.settings.SSL_VERIFY, "basepath": self.settings.MATTERMOST_API_PATH, "keepalive": True, "connect_kw_args": { "ping_interval": None }, }) self.driver.login() self.plugins = self._initialize_plugins(plugins) self.event_handler = EventHandler(self.driver, settings=self.settings, plugins=self.plugins) self.webhook_server = None if self.settings.WEBHOOK_HOST_ENABLED: self._initialize_webhook_server() self.running = False def _register_logger(self): logging.basicConfig( **{ "format": self.settings.LOG_FORMAT, "datefmt": "%m/%d/%Y %H:%M:%S", "level": logging.DEBUG if self.settings.DEBUG else logging.INFO, "filename": self.settings.LOG_FILE, "filemode": "w", }) # define and add a Handler which writes log messages to the sys.stdout self.console = logging.StreamHandler(stream=sys.stdout) self.console.setFormatter(logging.Formatter(self.settings.LOG_FORMAT)) logging.getLogger("").addHandler(self.console) def _initialize_plugins(self, plugins: Sequence[Plugin]): for plugin in plugins: plugin.initialize(self.driver, self.settings) return plugins def _initialize_webhook_server(self): self.webhook_server = WebHookServer( url=self.settings.WEBHOOK_HOST_URL, port=self.settings.WEBHOOK_HOST_PORT) self.driver.register_webhook_server(self.webhook_server) # Schedule the queue loop to the current event loop so that it starts together # with self.init_websocket. asyncio.get_event_loop().create_task( self.event_handler._check_queue_loop( self.webhook_server.event_queue)) def run(self): log.info(f"Starting bot {self.__class__.__name__}.") try: self.running = True self.driver.threadpool.start() # Start a thread to run potential scheduled jobs self.driver.threadpool.start_scheduler_thread( self.settings.SCHEDULER_PERIOD) # Start the webhook server on a separate thread if necessary if self.settings.WEBHOOK_HOST_ENABLED: self.driver.threadpool.start_webhook_server_thread( self.webhook_server) for plugin in self.plugins: plugin.on_start() # Start listening for events self.event_handler.start() except KeyboardInterrupt as e: raise e finally: # When either the event handler finishes (if we asked it to stop) or we # receive a KeyboardInterrupt, shut down the bot. self.stop() def stop(self): if not self.running: return log.info("Stopping bot.") # Shutdown the running plugins for plugin in self.plugins: plugin.on_stop() # Stop the threadpool self.driver.threadpool.stop() self.running = False
class Bot: """Base chatbot class. Can be either subclassed for custom functionality, or used as-is with custom plugins and settings. To start the bot, simply call bot.run(). """ def __init__( self, settings: Optional[Settings] = None, plugins: Optional[Sequence[Plugin]] = None, ): if plugins is None: plugins = [ExamplePlugin(), WebHookExample()] # Use default settings if none were specified. self.settings = settings or Settings() logging.basicConfig( **{ "format": "[%(asctime)s] %(message)s", "datefmt": "%m/%d/%Y %H:%M:%S", "level": logging.DEBUG if self.settings.DEBUG else logging.INFO, "stream": sys.stdout, }) self.driver = Driver({ "url": self.settings.MATTERMOST_URL, "port": self.settings.MATTERMOST_PORT, "token": self.settings.BOT_TOKEN, "scheme": self.settings.SCHEME, "verify": self.settings.SSL_VERIFY, }) self.driver.login() self.plugins = self._initialize_plugins(plugins) self.event_handler = EventHandler(self.driver, settings=self.settings, plugins=self.plugins) self.webhook_server = None if self.settings.WEBHOOK_HOST_ENABLED: self._initialize_webhook_server() def _initialize_plugins(self, plugins: Sequence[Plugin]): for plugin in plugins: plugin.initialize(self.driver, self.settings) return plugins def _initialize_webhook_server(self): self.webhook_server = WebHookServer( url=self.settings.WEBHOOK_HOST_URL, port=self.settings.WEBHOOK_HOST_PORT) self.driver.register_webhook_server(self.webhook_server) # Schedule the queue loop to the current event loop so that it starts together # with self.init_websocket. asyncio.get_event_loop().create_task( self.event_handler._check_queue_loop( self.webhook_server.event_queue)) def run(self): logging.info(f"Starting bot {self.__class__.__name__}.") try: self.driver.threadpool.start() # Start a thread to run potential scheduled jobs self.driver.threadpool.start_scheduler_thread( self.settings.SCHEDULER_PERIOD) # Start the webhook server on a separate thread if necessary if self.settings.WEBHOOK_HOST_ENABLED: self.driver.threadpool.start_webhook_server_thread( self.webhook_server) for plugin in self.plugins: plugin.on_start() # Start listening for events self.event_handler.start() except KeyboardInterrupt as e: self.stop() raise e def stop(self): logging.info("Stopping bot.") # Shutdown the running plugins for plugin in self.plugins: plugin.on_stop() # Stop the threadpool self.driver.threadpool.stop()