def _update_elaborator(self, update: dict): self.offset = update["update_id"] + 1 infos = Infos(self, update) if infos.is_edited_message or infos.is_channel_post or infos.is_edited_channel_post: log.d(f"Ignoring update of type {infos.update_type}") return if not self._callback: self.waiting_data = {} if infos.user and not infos.message.is_command: if infos.user.is_bot_owner and self._callback: log.d(f"Calling callback {self._callback.__name__}") self._callback = self._callback(infos) return elif infos.user: if infos.user.is_bot_owner and self._callback: if infos.message.command == "cancel": self._callback = None infos.reply("Operation cancelled.") return if infos.user.is_bot_owner and infos.message.command == "test": elaborator.elaborate_json_backup(infos) if infos.message.is_document: elaborator.elaborate_file(infos) elif infos.message.is_command: mongo_interface.increment_read_messages(self.bot_id) self._command_elaborator(infos) elif infos.is_callback_query: self._callback_elaborator(infos) elif infos.is_message: mongo_interface.increment_read_messages(self.bot_id) self._message_elaborator(infos)
def elaborate(infos: Infos): if not infos.message.is_text: return for t_type_elaborator in _t_type_elaborators: triggers = mongo_interface.get_triggers_of_type( infos.bot.bot_id, t_type_elaborator, infos.db.language) for trigger in triggers: if "\\@" in trigger.trigger: trigger.trigger, identifier = trigger.trigger.split("\\@") if identifier.lower() == "owner": identifier = str(infos.bot.owner_id) if identifier.isnumeric(): if infos.user.uid != int(identifier): continue if _t_type_elaborators[t_type_elaborator](infos, trigger): return if infos.is_to_bot: complete_dialog_sec(infos, "generic") bot_utils.forward_to_owner(infos) if infos.bot.automs_enabled: log.d("Autom enabled, elaborating...") if randint(_autom_min, _autom_max) == _autom_max: complete_dialog_sec(infos, "automatics") return
def get_stats(bot_id: int) -> Stats: stats = Stats.from_json(_get_db().bot_stats.find_one({"bot_id": bot_id})) if not stats: log.d(f"Registering stats for bot {bot_id}") stats = Stats(bot_id) _get_db().bot_stats.insert_one(dict(stats)) return stats
def test_d__byte(self): out = io.StringIO() log.logger.addHandler(StreamHandler(stream=out)) log.set_level(log.Level.DEBUG) log.d(b'message') self.assertEqual(out.getvalue(), "message\n")
def run(self): self.running = True self.start_time = time.time() log.d("Starting update loop") methods.send_message(self.token, self.owner_id, "Master, i just booted up!") while self.running: self._updater()
def execute(token: str, method: str, params: dict = None, post_data: dict = None): try: if post_data is not None: log.d(f"POST request to {_base_url}/bot???/{method}") res = _session.post(f"{_base_url}/bot{token}/{method}", params=params, files=post_data, timeout=_timeout) else: # log.d(f"GET request to {_base_url}/bot???/{method}") res = _session.get(f"{_base_url}/bot{token}/{method}", params=params, timeout=_timeout) except Timeout as err: log.e(f"Timed out request to {_base_url}/bot???/{method}: {err}") raise TelegramException( f"Timed out request to {_base_url}/bot???/{method}: {err}", "Timeout", [key for key in (params if params else [])], [params[key] for key in (params if params else [])]) status_code = res.status_code if status_code == 200: return res.json()["result"] error = f"{status_code} while executing {method}" try: args = [ res.json()["description"], error, [key for key in (params if params else [])], [params[key] for key in (params if params else [])] ] except JSONDecodeError: args = [ res.text, error, [key for key in (params if params else [])], [params[key] for key in params] ] if status_code == 409: raise Conflict(*args) if status_code == 404: raise NotFound(*args) if status_code == 403: raise Forbidden(*args) if status_code == 401: raise Unauthorized(*args) if status_code == 400: raise BadRequest(*args) raise TelegramException(*args)
def _load_data(self): log.d("Getting bot data from mongo") bot_data = mongo_interface.get_bot_data(self.token) self.owner_id = int(bot_data["owner_id"]) self.clean_start = bot_data["clean_start"] self.automs_enabled = bot_data[ "automs_enabled"] if "automs_enabled" in bot_data else False self.custom_command_symb = bot_data[ "command_symb"] if "command_symb" in bot_data else "/"
def maker_master_command(infos: Infos): if not infos.user.is_maker_owner: return False if infos.message.command in _maker_master_commands: log.d(f"Maker owner issued command {infos.message.command}") _maker_master_commands[infos.message.command](infos) return True return False
def __call(self, cmd): """ Execute a command. :param str cmd: Command string. :return: Return code of the command. :rtype int """ log.d(" > %s" % cmd) return subprocess.call(cmd, shell=True)
def owner_command(infos: Infos): if not infos.user.is_bot_owner: return False if infos.message.command in _owner_commands: log.d(f"Owner issued command {infos.message.command}") _owner_commands[infos.message.command](infos) return True return False
def detach_bot(token: str): log.i(f"Detaching bot {token}") for bot in variables.attached_bots: log.d(bot.token) if bot.token != str(token): continue variables.attached_bots.remove(bot) log.i(f"Bot {token} detached") return bot.stop() log.i(f"Bot {token} not found")
def __files(self, dir_path): """ Find files. :return: Iterator[str] """ for root, dirs, files in os.walk(dir_path): for file in files: ret = os.path.join(root, file) log.d(ret) yield ret return
def maker_command(infos: Infos): if not infos.bot.is_maker: return False if infos.message.command in _maker_commands: log.d(f"{infos.user.uid} issued maker " f"command {infos.message.command}") _maker_commands[infos.message.command](infos) return True return False
def attach_bots(tokens: list): log.d(f"Attaching {len(tokens)} bots") if not isinstance(tokens, list): log.w("You must pass a list to attach_bots," " operation cancelled.") else: for token in tokens: if is_bot_token(token): attach_bot_by_token(token) else: log.w(f"Invalid token {token} skipping.")
def _load_modules(): for module in config["modules"]["classes"]: try: if "." in module: class_name = module.split(".")[-1] module = ".".join(module.split(".")[:-1]) LoadedModule = getattr(importlib.import_module(module), class_name) log.d(f"Loading module: {module} with class {LoadedModule}") load_module(LoadedModule) except Exception as err: log.e(err) traceback.print_exc()
def get_dialog_probability(message: str) -> [Optional[int], str]: match = re.search(r"^{(\d{1,3})%}", message) prob: Optional[int] = None if match: prob = int(match.group(1)) if prob < 1: prob = 1 elif prob > 99: prob = 100 message = message.replace(match.group(0), "") log.d(f"Found probability in string: {prob}") return prob, message
def attach_bot_by_token(token: str): try: log.d(f"Attaching bot {token}") b = Bot(token) variables.attached_bots.append(b) log.d(f"Bot {b.bot_id} attached successfully") return True except Unauthorized: log.w(f"Wrong bot token: {token}") except TelegramException as error: error_name = error.__class__.__name__ log.e(f"Exception '{error_name}' " f"while initializing the bot: {error}") return False
def command(infos: Infos): triggers = mongo_interface.get_triggers_of_type(infos.bot.bot_id, "command", infos.db.language) for trigger in triggers: if elaborate_command(infos, trigger): return if infos.message.command in _commands: log.d(f"User issued command {infos.message.command}") _commands[infos.message.command](infos) return True return False
def complete_dialog_sec(infos: Infos, section: str): log.d(f"Elaborating reply of section {section}") dialogs: List[Dialog] = mongo_interface.get_dialogs_of_section( infos.bot.bot_id, section, infos.db.language) if not dialogs: log.d(f"No dialogs set for section {section} lang {infos.db.language}") infos.bot.notify( f"No dialogs set for section {section} lang {infos.db.language}") return dialog = reply_parser.reply_choice(dialogs) infos.reply(dialog.reply, parse_mode=None) mongo_interface.increment_dialog_usages(dialog)
def elaborate_triggers(triggers: dict, infos: Infos): log.d("Elaborating triggers....") mongo_interface.drop_triggers() final_trigger_list = [] for t_type in triggers: if isinstance(triggers[t_type], str): triggers[t_type] = [ trigger.replace("_", " ") for trigger in triggers[t_type].split() ] actual_triggers = triggers[t_type] section = None # TODO add support for importing commands if t_type == "equals": t_type = "equal" elif t_type == "contents": t_type = "content" elif t_type == "eteractions": t_type = "eteraction" elif t_type == "interactions": t_type = "interaction" else: if t_type in [ "bot_commands", "antispam time", "bot_comm_symbol", "day_parts", "notte", "giorno", "admin_actions" ]: continue section = t_type t_type = "interaction" for trigger in actual_triggers: trigger = update_dummies(trigger) n_t = Trigger(t_type, trigger, trigger if not section else section, infos.bot.bot_id, infos.db.user.language, 0) print(dict(n_t)) final_trigger_list.append(n_t) mongo_interface.add_triggers(final_trigger_list) log.d("Elaborated!")
def __parse(self, byte): """ Parse log line. :param bytes byte: :return: Parse result. If failed to decode, returns None time. :rtype Merger.Parsed """ try: s = byte[0:Merge.TIME_FMT_LEN].decode(Merge.FILE_ENCODE) t = dt.strptime(s, Merge.TIME_FMT) except Exception as e: log.d(e) return self.Parsed(line=byte) return self.Parsed(t, byte)
def reply(self, infos: Infos, text: str, quote: bool = True, markdown: bool = False, markup: List = None, nolink: bool = False): log.d("Replying with a message") methods.send_message( self.token, infos.chat.cid, text, reply_to_message_id=infos.message.message_id if quote else None, parse_mode="markdown" if markdown else None, reply_markup=markup, disable_web_page_preview=nolink)
def register_bot(token: str, owner_id: int): # Check first if it's a real bot token try: methods.get_me(token) _get_bots_collection().insert_one({ "token": token, "owner_id": owner_id, # TODO make a method to change the start mode "clean_start": True, # TODO implement languages "language": "IT" }) log.d(f"Registered new bot with ID {token.split(':')[0]}") return True except TelegramException as ex: log.e(f"Cannot register bot: {ex.message}") return False
def file(dirname, filename, timeout): """ Wait until a file created. :param str dirname: Directory path to be watched. :param str filename: File name to be watched. :param num timeout: How many seconds to be wait. [sec] :return: Is created the file. :rtype bool """ is_glob = filename.find('*') >= 0 handler = ChangeHandler(dirname, filename, is_glob) observer = Observer() observer.schedule(handler, dirname, recursive=False) observer.start() try: UPDATE_INTERVAL = 0.5 interval = UPDATE_INTERVAL while interval >= 0 and timeout > 0: time.sleep(0.1) timeout -= 0.1 interval -= 0.1 log.d('- time out remain %.2f, update interval %.2f' % (timeout, interval)) if handler.modified is True: log.i('- now writing... %s ' % filename) handler.modified = False interval = UPDATE_INTERVAL except KeyboardInterrupt: observer.stop() if not is_glob: is_exists = os.path.exists(os.path.join(dirname, filename)) if is_exists: if timeout <= 0: log.w("- The file still updating.") else: log.i("- time out") return False observer.stop() observer.join(timeout) return True
def elaborate_dialogs(dialogs: dict, infos: Infos): log.d("Elaborating dialogs...") mongo_interface.drop_dialogs() final_dialogs_list = [] for section in dialogs: if not dialogs[section]: continue for reply in dialogs[section]: n_d = Dialog(update_dummies(reply), section, infos.db.user.language, infos.bot.bot_id) print(dict(n_d)) final_dialogs_list.append(n_d) mongo_interface.add_dialogs(final_dialogs_list) log.d("Elaborated!")
def elaborate_file(infos: Infos): if not infos.user.is_bot_owner: return if not infos.message.document.file_name.endswith(".kb"): return log.d("Found a kitsu backup, trying to decrypt it...") content = infos.message.document.download() jts = json.loads(decode(_backup_password, content)) with open("resources/backup.json", "w") as f: f.write(json.dumps(jts)) triggers = jts["triggers"] dialogs = jts["dialogs"] elaborate_triggers(triggers, infos) elaborate_dialogs(dialogs, infos)
def to_it(infos): if infos.chat.is_private: infos.db.user.language = "IT" infos.db.update_user() if infos.chat.is_supergroup or infos.chat.is_group: log.d("Dummy found in a group") infos.db.group.language = "IT" infos.db.update_group() dialogs = mongo_interface.get_dialogs_of_section(infos.bot.bot_id, "speak.it", "IT") if not dialogs: # TODO handle better missing dialog return "Ok" reply_parser.execute(reply_parser.reply_choice(dialogs).reply, infos) return ""
def __exec_convert_tar(self, script_path): """ Execute conversion a tar file. :param str script_path: Script file path, which will be used by `sed` command. :return Output directory :rtype str """ out_dir = self.__tar_dir + Converter.DIR_SUFFIX for d, f in tqdm(list(self.__files())): # create output directory. dist = os.path.join(out_dir, d) if not os.path.exists(dist): os.makedirs(dist) # execute conversion sp = os.path.join(self.__tar_dir, d, f) dp = os.path.join(dist, f) log.d("- convert: %s" % dp) self.__call(Converter.SED_CMD_FMT % (script_path, sp, dp)) return out_dir
def run(threaded: bool = True, idle: bool = True, auto_attach: bool = True): log.i(f"Starting Kitsu Ultimate version {config['lotus']['version']}") _load_modules() if config["startup"]["drop"]["dialogs"]: log.i("Dropping dialogs") mongo_interface.drop_dialogs() if config["startup"]["drop"]["triggers"]: log.i("Dropping triggers") mongo_interface.drop_triggers() if config["startup"]["drop"]["bots"]: log.i("Dropping bots") mongo_interface.drop_bots() if config["startup"]["drop"]["groups"]: log.i("Dropping groups") mongo_interface.drop_groups() if config["startup"]["drop"]["users"]: log.i("Dropping users") mongo_interface.drop_users() if not threaded: log.i("Running in webhook mode") raise NotImplementedError() if auto_attach: log.d("Auto attaching bots from mongo") attach_bots_from_manager() log.i("Running attached bots") [lotus_interface.run_bot(bot) for bot in variables.attached_bots] variables.running = True if idle: lotus_interface.idle()
def execute(reply: str, infos, markup=None): if re.search(r"^\[.+]$", reply, re.DOTALL): threading.Thread(target=elaborate_multx, args=(reply, infos)).start() return match = re.search(r"{media:(\w{3}),(.+?)(,(.+))?}", reply) if match: log.d("Matched media regex") media_type = match.group(1) media_id = match.group(2) caption = match.group(4) if media_type == "stk": methods.send_sticker(infos.bot.token, infos.chat.cid, media_id, reply_markup=markup) elif media_type == "pht": methods.send_photo(infos.bot.token, infos.chat.cid, media_id, caption, reply_markup=markup) elif media_type == "aud": methods.send_audio(infos.bot.token, infos.chat.cid, media_id, reply_markup=markup) elif media_type == "voe": methods.send_voice(infos.bot.token, infos.chat.cid, media_id, reply_markup=markup) elif media_type == "doc": methods.send_doc(infos.bot.token, infos.chat.cid, media_id, reply_markup=markup) return reply, quote, nolink, markdown, markup_msg = parse(reply, infos) if not markup and markup_msg: markup = markup_msg if reply == "": log.d("Ignoring empty message") return log.d("Sending message") return methods.send_message( infos.bot.token, infos.chat.cid, reply, reply_to_message_id=infos.message.message_id if quote else None, parse_mode="markdown" if markdown else None, reply_markup=markup, disable_web_page_preview=nolink)