class TestPluginManager: def __init__(self): self.plugin_path = path / 'tests' / 'test_plugins' self.good_plugin = self.plugin_path / 'test_plugin_2.py' self.good_plugin_package = self.plugin_path / 'test_plugin_package' self.bad_plugin = self.plugin_path / 'bad_plugin' self.bad_path = self.plugin_path / 'bad_path.py' self.dependent_plugins = self.plugin_path / "dependent_plugins" self.plugin_manager = PluginManager(None) self.loop = None def setup(self): self.plugin_manager = PluginManager(None) self.loop = asyncio.new_event_loop() def test_bad_paths(self): assert_raises(FileNotFoundError, self.plugin_manager._load_module, self.bad_path) def test_load_good_plugins(self): self.plugin_manager.load_plugin(self.good_plugin) self.plugin_manager.load_plugin(self.good_plugin_package) self.plugin_manager.resolve_dependencies() assert_in("test_plugin_2", self.plugin_manager.list_plugins().keys()) assert_in("test_plugin_1", self.plugin_manager.list_plugins().keys()) def test_load_bad_plugin(self): with assert_raises(SyntaxError): self.plugin_manager.load_plugin(self.bad_plugin) self.plugin_manager.resolve_dependencies() def test_load_plugin_dir(self): self.plugin_manager.load_from_path(self.plugin_path) self.plugin_manager.resolve_dependencies() assert_in("test_plugin_2", self.plugin_manager.list_plugins()) assert_in("test_plugin_1", self.plugin_manager.list_plugins()) assert_in("bad_plugin", self.plugin_manager.failed) def test_the_do_method(self): self.plugin_manager.load_plugin(self.good_plugin) self.plugin_manager.load_plugin(self.good_plugin_package) self.plugin_manager.resolve_dependencies() result = self.loop.run_until_complete( self.plugin_manager.do("chat_sent", b"")) assert_equals(result, True) def test_dependency_check(self): with assert_raises(ImportError): self.plugin_manager.load_plugin(self.dependent_plugins / 'b.py') self.plugin_manager.resolve_dependencies() def test_dependency_resolution(self): self.plugin_manager.load_plugins([ self.dependent_plugins / 'a.py', self.dependent_plugins / 'b.py' ]) self.plugin_manager.resolve_dependencies() def test_circular_dependency_error(self): with assert_raises(ImportError): self.plugin_manager.load_plugin( self.dependent_plugins / 'circular.py') self.plugin_manager.resolve_dependencies() def test_empty_overrides(self): self.plugin_manager.resolve_dependencies() owners = self.loop.run_until_complete( self.plugin_manager.get_overrides()) assert_equal(owners, set()) def test_override(self): self.plugin_manager.load_plugin( self.plugin_path / 'test_plugin_package') self.plugin_manager.load_plugin(self.plugin_path / 'test_plugin_2.py') self.plugin_manager.resolve_dependencies() self.plugin_manager.activate_all() overrides = self.loop.run_until_complete( self.plugin_manager.get_overrides()) assert_equal(overrides, {'on_chat_sent'}) def test_override_caching(self): self.plugin_manager.load_plugin(self.plugin_path / 'test_plugin_2.py') assert_equal(self.plugin_manager._overrides, set()) assert_equal(self.plugin_manager._override_cache, set()) self.plugin_manager.activate_all() self.loop.run_until_complete(self.plugin_manager.get_overrides()) assert_is(self.plugin_manager._override_cache, self.plugin_manager._activated_plugins) cache = self.plugin_manager._override_cache self.loop.run_until_complete(self.plugin_manager.get_overrides()) assert_is(self.plugin_manager._override_cache, cache) def test_activate(self): self.plugin_manager.load_plugin( self.plugin_path / 'test_plugin_package') self.plugin_manager.load_plugin(self.plugin_path / 'test_plugin_2.py') self.plugin_manager.resolve_dependencies() self.plugin_manager.activate_all() assert_equal({x.name for x in self.plugin_manager._activated_plugins}, {'test_plugin_1', 'test_plugin_2'})
class TestPluginManager: def __init__(self): self.plugin_path = path / 'tests' / 'test_plugins' self.good_plugin = self.plugin_path / 'test_plugin_2.py' self.good_plugin_package = self.plugin_path / 'test_plugin_package' self.bad_plugin = self.plugin_path / 'bad_plugin' self.bad_path = self.plugin_path / 'bad_path.py' self.dependent_plugins = self.plugin_path / "dependent_plugins" self.plugin_manager = PluginManager(None) self.loop = None def setup(self): self.plugin_manager = PluginManager(None) self.loop = asyncio.new_event_loop() def test_bad_paths(self): assert_raises(FileNotFoundError, self.plugin_manager._load_module, self.bad_path) def test_load_good_plugins(self): self.plugin_manager.load_plugin(self.good_plugin) self.plugin_manager.load_plugin(self.good_plugin_package) self.plugin_manager.resolve_dependencies() assert_in("test_plugin_2", self.plugin_manager.list_plugins().keys()) assert_in("test_plugin_1", self.plugin_manager.list_plugins().keys()) def test_load_bad_plugin(self): with assert_raises(SyntaxError): self.plugin_manager.load_plugin(self.bad_plugin) self.plugin_manager.resolve_dependencies() def test_load_plugin_dir(self): self.plugin_manager.load_from_path(self.plugin_path) self.plugin_manager.resolve_dependencies() assert_in("test_plugin_2", self.plugin_manager.list_plugins()) assert_in("test_plugin_1", self.plugin_manager.list_plugins()) assert_in("bad_plugin", self.plugin_manager.failed) def test_the_do_method(self): self.plugin_manager.load_plugin(self.good_plugin) self.plugin_manager.load_plugin(self.good_plugin_package) self.plugin_manager.resolve_dependencies() result = self.loop.run_until_complete( self.plugin_manager.do("chat_sent", b"")) assert_equals(result, True) def test_dependency_check(self): with assert_raises(ImportError): self.plugin_manager.load_plugin(self.dependent_plugins / 'b.py') self.plugin_manager.resolve_dependencies() def test_dependency_resolution(self): self.plugin_manager.load_plugins( [self.dependent_plugins / 'a.py', self.dependent_plugins / 'b.py']) self.plugin_manager.resolve_dependencies() def test_circular_dependency_error(self): with assert_raises(ImportError): self.plugin_manager.load_plugin(self.dependent_plugins / 'circular.py') self.plugin_manager.resolve_dependencies() def test_empty_overrides(self): self.plugin_manager.resolve_dependencies() owners = self.loop.run_until_complete( self.plugin_manager.get_overrides()) assert_equal(owners, set()) def test_override(self): self.plugin_manager.load_plugin(self.plugin_path / 'test_plugin_package') self.plugin_manager.load_plugin(self.plugin_path / 'test_plugin_2.py') self.plugin_manager.resolve_dependencies() self.plugin_manager.activate_all() overrides = self.loop.run_until_complete( self.plugin_manager.get_overrides()) assert_equal(overrides, {'on_chat_sent'}) def test_override_caching(self): self.plugin_manager.load_plugin(self.plugin_path / 'test_plugin_2.py') assert_equal(self.plugin_manager._overrides, set()) assert_equal(self.plugin_manager._override_cache, set()) self.plugin_manager.activate_all() self.loop.run_until_complete(self.plugin_manager.get_overrides()) assert_is(self.plugin_manager._override_cache, self.plugin_manager._activated_plugins) cache = self.plugin_manager._override_cache self.loop.run_until_complete(self.plugin_manager.get_overrides()) assert_is(self.plugin_manager._override_cache, cache) def test_activate(self): self.plugin_manager.load_plugin(self.plugin_path / 'test_plugin_package') self.plugin_manager.load_plugin(self.plugin_path / 'test_plugin_2.py') self.plugin_manager.resolve_dependencies() self.plugin_manager.activate_all() assert_equal({x.name for x in self.plugin_manager._activated_plugins}, {'test_plugin_1', 'test_plugin_2'})
def test_list_plugins(): print PluginManager.list_plugins()
class Bot: """ Singleton Bot class that acts as the controller for messages received and sent to Telegram. This file should be run when starting the bot currently. ... Methods ------- start() Starts the bot where it will check for updates received from Telegram and send replies based on Plugins and Messages. reload_plugins() Reloads all plugins managed in self.plugin_manager, incorporating new changes made enable_plugin(plugin_name) Enables a plugin with a specific name disable_plugin(plugin_name) Disables a plugin with a specific name plugin_help(plugin_name) Returns a string containing the help message from a Plugin's get_help method list_plugins() Returns a str listing all plugins get_updates(last_update) Gets message updates from Telegram based on those last received. send_message(id, message) Sends a message to Telegram. send_photo(id, message, file_name) Sends a photo to Telegram. """ def __init__(self, config): """ Sets up initial bot data and the PluginManager which loads inital plugins. ... Parameters ---------- config: Config Configuration object containing data found within the bot's configuration file. """ self.logger = logging.getLogger('bot_log') self.directory = config.bot_dir self.base_url = "https://api.telegram.org/bot"+config.token+"/" self.sleep_interval = config.sleep_interval self.username = json.loads(requests.get(self.base_url + "getMe").text)["result"]["username"] self.plugin_manager = PluginManager(config, self) print(self.plugin_manager.list_commands()) print(self.plugin_manager.list_listeners()) def start(self, thread): """ Receives and sends messages to Telegram forever. Responses made by the bot are based on functionality contained within Plugins. """ last_update = 0 while not thread.stopped(): updates = self.get_updates(last_update) for update in updates["result"]: last_update = update["update_id"] if "message" in update: message = Message(update["message"]) self.logger.info(str(last_update)+": "+message.sent_from.username) if message.is_command: self.logger.info("Command received, processing plugins") t = threading.Thread(target = self.plugin_manager.process_plugin, args = (self, message)) t.setDaemon(True) t.start() else: t = threading.Thread(target = self.plugin_manager.process_message, args = (self, message)) t.setDaemon(True) t.start() self.logger.warn("Ending telegram update loop due to thread manually being killed") def reload_plugins(self): """ Reloads all plugins managed in self.plugin_manager, incorporating new changes made """ self.plugin_manager.reload_plugins(self) return True def enable_plugin(self, plugin_name): """ Enables a plugin with a specific name """ self.logger.info("Attempting to enable plugin with name {}".format(plugin_name)) return self.plugin_manager.enable_plugin(plugin_name) def disable_plugin(self, plugin_name): """ Disables a plugin with a specific name """ self.logger.info("Attempting to disable plugin with name {}".format(plugin_name)) return self.plugin_manager.disable_plugin(plugin_name) def plugin_help(self, plugin_name): """ Returns a string containing the help message from a Plugin's get_help method """ self.logger.info("Requested help message from plugin with name {}".format(plugin_name)) return self.plugin_manager.plugin_help(plugin_name) def list_plugins(self): """ Returns a str listing all plugins """ self.logger.info("Request recieved to list all plugins") return self.plugin_manager.list_plugins() def get_updates(self, last_update): """ Gets message updates from Telegram based on those last received. ... Parameters ---------- last_update: json Data received from telegram dictating new messages that were send/visible to the bot. """ return json.loads(requests.get(self.base_url + 'getUpdates', params=dict(offset=(last_update+1))).text) def send_message(self, id, message): """ Sends a string message to a specific telegram chatroom containing the designated id. ... Parameters ---------- id: str The id of the chatroom to send a message to. message: str The message to be send to a chatroom. """ self.logger.info("Sending message ({}) to channel with id {}".format(message, id)) return requests.get(self.base_url + 'sendMessage', params=dict(chat_id=id, text=message)) def send_photo(self, id, message, file_name): """ Sends a photo found with the designated filename with an optional caption string message to a chatroom containing the designated id ... Parameters ---------- id: str The id of the chatroom to send a message to. message: optional An optional string to send with an image as a caption file_name: str The filename of the photo to send to a Telegram chatroom """ files = {'photo': open(file_name, 'rb')} data = dict(chat_id=id, caption=message) self.logger.info("Sending photo with caption ({}) with filename ({}) to channel with id {}".format(message, file_name, id)) return requests.get(self.base_url + 'sendPhoto', files=files, data=data)