def test_fd_disallowed_module(hallo_getter): hallo, test_server, test_channel, test_user = hallo_getter({}) # Create a blank function dispatcher fd = FunctionDispatcher(set(), hallo) try: # Try and load a module assert not fd.reload_module("euler") finally: fd.close()
def load_json() -> 'Hallo': """ Loads up the json configuration and creates a new Hallo object :return: new Hallo object :rtype: Hallo """ try: with open("config/config.json", "r") as f: json_obj = json.load(f) except (OSError, IOError): error = MessageError("No current config, loading from default.") logger.error(error.get_log_line()) with open("config/config-default.json", "r") as f: json_obj = json.load(f) # Create new hallo object new_hallo = Hallo() new_hallo.default_nick = json_obj["default_nick"] new_hallo.default_prefix = json_obj["default_prefix"] new_hallo.default_full_name = json_obj["default_full_name"] new_hallo.function_dispatcher = FunctionDispatcher.from_json( json_obj["function_dispatcher"], new_hallo ) # User groups must be done before servers, as users will try and look up and add user groups! for user_group in json_obj["user_groups"]: new_hallo.add_user_group(UserGroup.from_json(user_group, new_hallo)) for server in json_obj["servers"]: new_server = new_hallo.server_factory.new_server_from_json(server) new_hallo.add_server(new_server) if "permission_mask" in json_obj: new_hallo.permission_mask = PermissionMask.from_json( json_obj["permission_mask"] ) for api_key in json_obj["api_keys"]: new_hallo.add_api_key(api_key, json_obj["api_keys"][api_key]) return new_hallo
def test_help_mock_func_disp(): # Set up mock objects mock_hallo = Hallo() mock_func_disp = FunctionDispatcher(set(), mock_hallo) mock_hallo.function_dispatcher = mock_func_disp mock_func_disp.load_function(None, FunctionMock) mock_func_disp.load_function(None, FunctionMockNoDoc) mock_func_disp.load_function(None, Help) mock_server = ServerMock(mock_hallo) mock_server.name = "test_serv1" mock_user = mock_server.get_user_by_address("test_user1".lower(), "test_user1") # Test things mock_func_disp.dispatch(EventMessage(mock_server, None, mock_user, "help")) data = mock_server.get_send_data(1, mock_user, EventMessage) assert "error" not in data[0].text.lower() assert "list of available functions:" in data[0].text.lower() assert "function mock" in data[0].text.lower() assert "function no doc" in data[0].text.lower()
def setUp(self): print("Starting test: " + self.id()) self.start_time = time.time() # Create a Hallo self.hallo = Hallo() # Only the required modules, only 1 (mock) server # Todo: specify modules by test? self.function_dispatcher = FunctionDispatcher( {"convert", "random", "server_control", "subscriptions"}, self.hallo ) self.hallo.function_dispatcher = self.function_dispatcher print( "Running test: " + self.id() + ". Init took: " + str(time.time() - self.start_time) + " seconds." ) self.server = ServerMock(self.hallo) self.server.name = "mock-server" self.hallo.add_server(self.server) # Start hallo thread self.hallo_thread = Thread(target=self.hallo.start,) self.hallo_thread.start() # Create test users and channel, and configure them self.hallo_user = self.server.get_user_by_address( self.server.get_nick().lower(), self.server.get_nick() ) self.test_user = self.server.get_user_by_address("test", "test") self.test_user.online = True self.test_chan = self.server.get_channel_by_address("#test", "#test") self.test_chan.in_channel = True self.test_chan.add_user(self.hallo_user) self.test_chan.add_user(self.test_user) # Wait until hallo is open count = 0 while not self.hallo.open: time.sleep(0.01) count += 1 assert count < 1000, "Hallo took too long to start." if count > 1000: break # Clear any data in the server self.server.get_send_data() # Print test startup time print( "Running test: " + self.id() + ". Startup took: " + str(time.time() - self.start_time) + " seconds." ) self.start_time = time.time()
def start(self) -> None: # If no function dispatcher, create one # TODO: manual FunctionDispatcher construction, user input? if self.function_dispatcher is None: self.function_dispatcher = FunctionDispatcher( { "channel_control", "convert", "hallo_control", "lookup", "math", "permission_control", "random", "server_control", }, self, ) # If no servers, ask for a new server if len(self.server_list) == 0 or all( [not server.get_auto_connect() for server in self.server_list] ): self.manual_server_connect() # Connect to auto-connect servers logger.info("Connecting to servers") for server in self.server_list: if server.get_auto_connect(): server.start() count = 0 while not self.connected_to_any_servers(): time.sleep(0.01) count += 1 if count > 6000: self.open = False error = MessageError("No servers managed to connect in 60 seconds.") logger.error(error.get_log_line()) return self.open = True # Main loop, sticks around throughout the running of the bot logger.info("Connected to all servers.") self.core_loop_time_events()
def test_init(): # Create some basic stuff test_modules = {"euler"} test_hallo = Hallo() # Create function dispatcher fd = FunctionDispatcher(test_modules, test_hallo) test_hallo.function_dispatcher = fd try: # Check basic class variable setting assert (fd.hallo == test_hallo ), "Hallo object was not set correctly in FunctionDispatcher." assert ( fd.module_list == test_modules ), "Module list was not imported correctly by FunctionDispatcher." # Check that module reloading has done things assert len(fd.function_dict) == len( test_modules), "Modules were not loaded to function dictionary." assert len(fd.function_names ) != 0, "Functions were not added to function_names" finally: fd.close() test_hallo.close()
class Hallo: def __init__(self): self.default_nick: str = "Hallo" self.default_prefix: Union[bool, str] = False self.default_full_name: str = "HalloBot HalloHost HalloServer :an irc bot by spangle" self.open: bool = False self.user_group_list: Set[UserGroup] = set() self.server_list: Set[Server] = set() self.api_key_list: Dict[str, str] = {} self.server_factory: ServerFactory = ServerFactory(self) self.permission_mask: PermissionMask = PermissionMask() # TODO: manual FunctionDispatcher construction, user input? self.function_dispatcher: FunctionDispatcher = None def start(self) -> None: # If no function dispatcher, create one # TODO: manual FunctionDispatcher construction, user input? if self.function_dispatcher is None: self.function_dispatcher = FunctionDispatcher( { "channel_control", "convert", "hallo_control", "lookup", "math", "permission_control", "random", "server_control", }, self, ) # If no servers, ask for a new server if len(self.server_list) == 0 or all( [not server.get_auto_connect() for server in self.server_list] ): self.manual_server_connect() # Connect to auto-connect servers logger.info("Connecting to servers") for server in self.server_list: if server.get_auto_connect(): server.start() count = 0 while not self.connected_to_any_servers(): time.sleep(0.01) count += 1 if count > 6000: self.open = False error = MessageError("No servers managed to connect in 60 seconds.") logger.error(error.get_log_line()) return self.open = True # Main loop, sticks around throughout the running of the bot logger.info("Connected to all servers.") self.core_loop_time_events() def connected_to_any_servers(self) -> bool: auto_connecting_servers = [ server for server in self.server_list if server.auto_connect ] connected_list = [server.is_connected() for server in auto_connecting_servers] return any(connected_list) def core_loop_time_events(self) -> None: """ Runs a loop to keep hallo running, while calling time events with the FunctionDispatcher passive dispatcher """ last_date_time = datetime.now() while self.open: now_date_time = datetime.now() try: if now_date_time.second != last_date_time.second: second = EventSecond() self.function_dispatcher.dispatch_passive(second) if now_date_time.minute != last_date_time.minute: logger.debug("Core heartbeat") heartbeat.update_heartbeat(heartbeat_app_name) minute = EventMinute() self.function_dispatcher.dispatch_passive(minute) if now_date_time.hour != last_date_time.hour: hour = EventHour() self.function_dispatcher.dispatch_passive(hour) if now_date_time.day != last_date_time.day: day = EventDay() self.function_dispatcher.dispatch_passive(day) except Exception as e: logger.error("Error sending core time loop event.", exc_info=e) last_date_time = now_date_time time.sleep(0.1) self.close() def save_json(self) -> None: """ Saves the whole hallo config to a JSON file :return: None """ json_obj = dict() json_obj["default_nick"] = self.default_nick json_obj["default_prefix"] = self.default_prefix json_obj["default_full_name"] = self.default_full_name json_obj["function_dispatcher"] = self.function_dispatcher.to_json() json_obj["servers"] = [] for server in self.server_list: json_obj["servers"].append(server.to_json()) json_obj["user_groups"] = [] for user_group in self.user_group_list: json_obj["user_groups"].append(user_group.to_json()) if not self.permission_mask.is_empty(): json_obj["permission_mask"] = self.permission_mask.to_json() json_obj["api_keys"] = {} for api_key_name in self.api_key_list: json_obj["api_keys"][api_key_name] = self.api_key_list[api_key_name] # Write json to file with open("config/config.json", "w+") as f: json.dump(json_obj, f, indent=2) @staticmethod def load_json() -> 'Hallo': """ Loads up the json configuration and creates a new Hallo object :return: new Hallo object :rtype: Hallo """ try: with open("config/config.json", "r") as f: json_obj = json.load(f) except (OSError, IOError): error = MessageError("No current config, loading from default.") logger.error(error.get_log_line()) with open("config/config-default.json", "r") as f: json_obj = json.load(f) # Create new hallo object new_hallo = Hallo() new_hallo.default_nick = json_obj["default_nick"] new_hallo.default_prefix = json_obj["default_prefix"] new_hallo.default_full_name = json_obj["default_full_name"] new_hallo.function_dispatcher = FunctionDispatcher.from_json( json_obj["function_dispatcher"], new_hallo ) # User groups must be done before servers, as users will try and look up and add user groups! for user_group in json_obj["user_groups"]: new_hallo.add_user_group(UserGroup.from_json(user_group, new_hallo)) for server in json_obj["servers"]: new_server = new_hallo.server_factory.new_server_from_json(server) new_hallo.add_server(new_server) if "permission_mask" in json_obj: new_hallo.permission_mask = PermissionMask.from_json( json_obj["permission_mask"] ) for api_key in json_obj["api_keys"]: new_hallo.add_api_key(api_key, json_obj["api_keys"][api_key]) return new_hallo def add_user_group(self, user_group: UserGroup) -> None: """ Adds a new UserGroup to the UserGroup list :param user_group: UserGroup to add to the hallo object's list of user groups """ self.user_group_list.add(user_group) def get_user_group_by_name(self, user_group_name: str) -> Optional[UserGroup]: """ Returns the UserGroup with the specified name :param user_group_name: Name of user group to search for :return: User Group matching specified name, or None """ for user_group in self.user_group_list: if user_group_name == user_group.name: return user_group return None def remove_user_group(self, user_group: UserGroup) -> None: """ Removes a user group specified by name :param user_group: Name of the user group to remove from list :type user_group: UserGroup """ self.user_group_list.remove(user_group) def add_server(self, server: Server) -> None: """ Adds a new server to the server list :param server: Server to add to Hallo's list of servers :type server: Server.Server """ self.server_list.add(server) def get_server_by_name(self, server_name: str) -> Optional[Server]: """ Returns a server matching the given name :param server_name: name of the server to search for :return: Server matching specified name of None """ for server in self.server_list: if server.name.lower() == server_name.lower(): return server return None def remove_server(self, server: Server) -> None: """ Removes a server from the list of servers :param server: The server to remove :type server: Server.Server """ self.server_list.remove(server) def remove_server_by_name(self, server_name: str) -> None: """ Removes a server, specified by name, from the list of servers :param server_name: Name of the server to remove """ for server in self.server_list: if server.name.lower() == server_name.lower(): self.server_list.remove(server) def close(self) -> None: """Shuts down the entire program""" for server in self.server_list: if server.state != Server.STATE_CLOSED: server.disconnect() self.function_dispatcher.close() self.save_json() self.open = False def rights_check(self, right_name: str) -> bool: """ Checks the value of the right with the specified name. Returns boolean :param right_name: name of the user right to search for :return: Boolean, whether or not the specified right is given """ right_value = self.permission_mask.get_right(right_name) # If PermissionMask contains that right, return it. if right_value in [True, False]: return right_value # If it's a function right, go to default_function right if right_name.startswith("function_"): return self.rights_check("default_function") # If default_function is not defined, define and return it as True if right_name == "default_function": self.permission_mask.set_right("default_function", True) return True else: # Else, define and return False self.permission_mask.set_right(right_name, False) return False def add_api_key(self, name: str, key: str) -> None: """ Adds an api key to the list, or overwrites one. :param name: Name of the API to add :type name: str :param key: The actual API key to use :type key: str """ self.api_key_list[name] = key def get_api_key(self, name: str) -> Optional[str]: """ Returns a specified api key. :param name: Name of the API key to retrieve """ if name in self.api_key_list: return self.api_key_list[name] return None def manual_server_connect(self) -> None: # TODO: add ability to connect to non-IRC servers logger.error( "No servers have been loaded or connected to. Please connect to an IRC server." ) # godNick = input("What nickname is the bot operator using? [deer-spangle] ") # godNick = godNick.replace(' ', '') # if godNick == '': # godNick = 'deer-spangle' # TODO: do something with godNick server_addr = input( "What server should the bot connect to? [irc.freenode.net:6667] " ) server_addr = server_addr.replace(" ", "") if server_addr == "": server_addr = "irc.freenode.net:6667" server_url = server_addr.split(":")[0] server_port = int(server_addr.split(":")[1]) server_match = re.match( r"([a-z\d.-]+\.)?([a-z\d-]{1,63})\.([a-z]{2,3}\.[a-z]{2}|[a-z]{2,6})", server_url, re.I, ) server_name = server_match.group(2) # Create the server object new_server = ServerIRC(self, server_name, server_url, server_port) # Add new server to server list self.add_server(new_server) # Save XML self.save_json() logger.info("Config file saved.")