def main(self, config_data, additional_general_config): if "owners" in Config.General: self.owners = Config.General["owners"] else: sys.exit("[Chatbot] FATAL: no owners found. Please update Config.py.") if "privileged_users" in config_data: self.privileged_users = config_data["privileged_users"] if "github" in Config.General: self.github = Config.General["github"] else: self.github = "https://github.com/ProgramFOX/SE-Chatbot" if "owner_name" in Config.General: self.owner_name = Config.General["owner_name"] else: sys.exit("[Chatbot] FATAL: no owner name found. Please update Config.py.") if "chatbot_name" in Config.General: self.chatbot_name = Config.General["chatbot_name"] else: sys.exit("[Chatbot] FATAL: no chatbot name found. Please update Config.py.") # self.setup_logging() # if you want to have logging, un-comment this line if "site" in config_data: self.site = config_data["site"] print("Site: %s" % self.site) else: self.site = input("Site: ") for o in self.owners: if self.site in o: self.owner_ids.append(o[self.site]) if len(self.owner_ids) < 1: sys.exit("[Chatbot] FATAL: no owners found for this site: %s." % self.site) for p in self.privileged_users: if self.site in p: self.privileged_user_ids.append(p[self.site]) if "room" in config_data: room_number = config_data["room"] print("Room number: %i" % room_number) else: room_number = int(input("Room number: ")) if "prefix" in config_data: self.prefix = config_data["prefix"] else: self.prefix = '>>' print("Prefix: %s" % self.prefix) if "email" in Config.General: email = Config.General["email"] elif "email" in additional_general_config: email = additional_general_config["email"] else: email = input("Email address: ") self.client = Client(self.site) # Setting the timeout down to 5 fixes random SSL errors when terminating. # The bot's timeout on exit is 5; the request timeout is 30 by default. Requests overrun the # bot timeout, get force-closed, and cause errors. self.client._br.request_timeout = 5.0 try: if "password" in Config.General: # I would not recommend to store the password in Config.py password = Config.General["password"] self.client.login(email, password) elif "password" in additional_general_config: password = additional_general_config["password"] self.client.login(email, password) else: for attempts in range(3): try: password = getpass.getpass("Password: "******"Incorrect password.") else: raise except LoginError: sys.exit("[Chatbot] FATAL: Incorrect password, shutting down.") self.room = self.client.get_room(room_number) self.room.join() if "message" not in additional_general_config: bot_message = "Bot started." else: bot_message = additional_general_config["message"] if bot_message is not None: self.room.send_message(bot_message) on_loads = self.modules.get_on_load_methods() for on_load in on_loads: on_load(self) self.room.watch_socket(self.on_event) while self.running: inputted = input("<< ") if inputted.strip() == "": continue if inputted.startswith("$") and len(inputted) > 2: command_in = inputted[2:] cmd_handler = ConsoleCommandHandler(self, inputted[1] == "+") command_out = self.command(command_in, cmd_handler, None) if command_out is not False and command_out is not None: cmd_handler.reply(command_out) else: self.room.send_message(inputted)
class Chatbot: def __init__(self): self.room = None self.client = None self.privileged_users = [] self.owners = [] self.owner_name = "" self.chatbot_name = "" self.enabled = True self.running = True self.site = "" self.owner_ids = [] self.privileged_user_ids = [] self.save_subdirs = ['main'] self.modules = MetaModule(ModuleManifest.module_file_names, self, 'all') try: SaveIO.set_subdirs(self.save_subdirs) except DuplicateDirectoryException as e: if "-q" not in sys.argv: print("[Chatbot] WARNING: there are modules with the same save directory: " + str(e)) SaveIO.create_if_not_exists(SaveIO.data_dir) del self.save_subdirs duplicates = self.get_duplicate_commands() if duplicates and "-q" not in sys.argv: print('[Chatbot] WARNING: there are commands with the same name: ' + str(duplicates)) def main(self, config_data, additional_general_config): if "owners" in Config.General: self.owners = Config.General["owners"] else: sys.exit("[Chatbot] FATAL: no owners found. Please update Config.py.") if "privileged_users" in config_data: self.privileged_users = config_data["privileged_users"] if "github" in Config.General: self.github = Config.General["github"] else: self.github = "https://github.com/ProgramFOX/SE-Chatbot" if "owner_name" in Config.General: self.owner_name = Config.General["owner_name"] else: sys.exit("[Chatbot] FATAL: no owner name found. Please update Config.py.") if "chatbot_name" in Config.General: self.chatbot_name = Config.General["chatbot_name"] else: sys.exit("[Chatbot] FATAL: no chatbot name found. Please update Config.py.") # self.setup_logging() # if you want to have logging, un-comment this line if "site" in config_data: self.site = config_data["site"] print("Site: %s" % self.site) else: self.site = input("Site: ") for o in self.owners: if self.site in o: self.owner_ids.append(o[self.site]) if len(self.owner_ids) < 1: sys.exit("[Chatbot] FATAL: no owners found for this site: %s." % self.site) for p in self.privileged_users: if self.site in p: self.privileged_user_ids.append(p[self.site]) if "room" in config_data: room_number = config_data["room"] print("Room number: %i" % room_number) else: room_number = int(input("Room number: ")) if "prefix" in config_data: self.prefix = config_data["prefix"] else: self.prefix = '>>' print("Prefix: %s" % self.prefix) if "email" in Config.General: email = Config.General["email"] elif "email" in additional_general_config: email = additional_general_config["email"] else: email = input("Email address: ") self.client = Client(self.site) # Setting the timeout down to 5 fixes random SSL errors when terminating. # The bot's timeout on exit is 5; the request timeout is 30 by default. Requests overrun the # bot timeout, get force-closed, and cause errors. self.client._br.request_timeout = 5.0 try: if "password" in Config.General: # I would not recommend to store the password in Config.py password = Config.General["password"] self.client.login(email, password) elif "password" in additional_general_config: password = additional_general_config["password"] self.client.login(email, password) else: for attempts in range(3): try: password = getpass.getpass("Password: "******"Incorrect password.") else: raise except LoginError: sys.exit("[Chatbot] FATAL: Incorrect password, shutting down.") self.room = self.client.get_room(room_number) self.room.join() if "message" not in additional_general_config: bot_message = "Bot started." else: bot_message = additional_general_config["message"] if bot_message is not None: self.room.send_message(bot_message) on_loads = self.modules.get_on_load_methods() for on_load in on_loads: on_load(self) self.room.watch_socket(self.on_event) while self.running: inputted = input("<< ") if inputted.strip() == "": continue if inputted.startswith("$") and len(inputted) > 2: command_in = inputted[2:] cmd_handler = ConsoleCommandHandler(self, inputted[1] == "+") command_out = self.command(command_in, cmd_handler, None) if command_out is not False and command_out is not None: cmd_handler.reply(command_out) else: self.room.send_message(inputted) def get_duplicate_commands(self): checked_cmds = [] dupe_cmds = [] all_cmds = self.modules.list_commands() for command in all_cmds: if command.name not in checked_cmds: checked_cmds.append(command.name) else: if command.name not in dupe_cmds: dupe_cmds.append(command.name) return dupe_cmds def setup_logging(self): # logging method taken from ChatExchange/examples/chat.py logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) logger.setLevel(logging.DEBUG) # In addition to the basic stderr logging configured globally # above, we'll use a log file for chatexchange.client. wrapper_logger = logging.getLogger('chatexchange.client') wrapper_handler = logging.handlers.TimedRotatingFileHandler( filename='client.log', when='midnight', delay=True, utc=True, backupCount=7, ) wrapper_handler.setFormatter(logging.Formatter( "%(asctime)s: %(levelname)s: %(threadName)s: %(message)s" )) wrapper_logger.addHandler(wrapper_handler) def check_existence_and_chars(self, cmd_name, content): cmd_list = self.modules.list_commands() allowed = -1 disallowed = -1 for cmd in cmd_list: if cmd.name == cmd_name or (cmd.aliases is not None and cmd_name in cmd.aliases): allowed = cmd.allowed_chars disallowed = cmd.disallowed_chars break if allowed == -1: return False, False for c in content: if disallowed is not None and c in disallowed: return True, False if allowed is not None and c not in allowed: return True, False return True, True def requires_special_arg_parsing(self, cmd_name): cmd_list = self.modules.list_commands() for cmd in cmd_list: if cmd.name == cmd_name: return cmd.special_arg_parsing is not None return False def do_special_arg_parsing(self, cmd_name, full_cmd): cmd_list = self.modules.list_commands() for cmd in cmd_list: if cmd.name == cmd_name and cmd.special_arg_parsing is not None: return cmd.special_arg_parsing(full_cmd) return False def on_event(self, event, client): if (not self.enabled and event.user.id not in self.owner_ids) \ or not self.running: return watchers = self.modules.get_event_watchers() for w in watchers: w(event, client, self) if not (isinstance(event, MessagePosted) or isinstance(event, MessageEdited)): return if event.user.id == self.client.get_me().id: return message = event.message content = Message(event.message.id, client).content_source fixed_font = is_fixed_font(content) if fixed_font: fixed_font = True content = fixed_font_to_normal(content) content = re.sub(r"^%s\s+" % self.prefix, self.prefix, content) content = re.sub("(^[^ \r\n]+)(\r?\n)", r"\1 ", content) if not fixed_font: stripped_content = re.sub(r"\s+", " ", content) stripped_content = stripped_content.strip() else: stripped_content = content parts = stripped_content.split(" ") pinged = parts[0].lower().startswith("@" + self.chatbot_name.lower()) if not pinged and not parts[0].startswith(self.prefix): return cmd_args = stripped_content[len(self.prefix):] if not pinged else stripped_content[1+len(self.chatbot_name)+1:] #DEBUG if "--debug" in sys.argv: if pinged: print("Received command over ping, stripped_content is '{0}'".format(stripped_content[1 + len(self.chatbot_name) + 1:])) # "@" + self.chatbot_name + " " print("Received command over ping, content is '{0}'".format(content[1 + len(self.chatbot_name) + 1:])) # "@" + self.chatbot_name + " " else: print("Received command over prefix, stripped_content is '{0}'".format(stripped_content[len(self.prefix):])) print("Received command over prefix, content is '{0}'".format(content[len(self.prefix):])) #DEBUG END if self.requires_special_arg_parsing(cmd_args.split(" ")[0]): cmd_args = content[len(self.prefix):] if not pinged else content[1+len(self.chatbot_name)+1:] output = self.command(cmd_args, message, event) if output is not False and output is not None: output_with_reply = ":%i %s" % (message.id, output) if len(output_with_reply) > 500 and "\n" not in output_with_reply: fixed_formatted_reply = self.automatic_fixed_formatting(output_with_reply) if not fixed_formatted_reply: #message.reply("Output would be longer than 500 characters (the limit for single-line messages), so only the first 500 characters are posted now.") message.reply("Automatic formatting of output failed or is disabled, printing the first 500 characters (the limit for single-line messages) instead.") self.room.send_message(output_with_reply[:500]) else: self.room.send_message(fixed_formatted_reply, False) else: self.room.send_message(output_with_reply, False) def command(self, cmd, msg, event): cmd_args = cmd.split(' ') cmd_name = cmd_args[0].lower() args = cmd_args[1:] exists, allowed = self.check_existence_and_chars(cmd_name, ' '.join(args)) if not exists: if "--debug" in sys.argv: print("Command not found: cmd = '{0}', msg = '{1}'".format(cmd, msg)) return "Command not found." if not allowed: return "Command contains invalid characters." if self.requires_special_arg_parsing(cmd_name): args = self.do_special_arg_parsing(cmd_name, cmd) if args is False: return "Argument parsing failed." return self.modules.command(cmd_name, args, msg, event) def bot_stopping(self): on_stops = self.modules.get_on_stop_methods() for on_stop in on_stops: on_stop(self) # Converts text to fixed format at latest possible breakpoint. # This method is recursive. def automatic_fixed_formatting(self, text): #To disable this function, uncomment the next line #return False if len(text) <= 500: #Recursion anchor return text index = text.rfind(" ", 0, 500) if index == -1: #Failed to find a valid breakpoint in the current segment. Nothing we can do here now. return False result = text[:index] + "\n" other_text = self.automatic_fixed_formatting(text[index+1:]) # !RECURSION! if not other_text: #Failed to find a valid breakpoint in a later segment. Nothing we can do here now. return False return result + other_text