from telegram.ext import Updater, MessageHandler, Filters import datetime import codecs import time import json import sys import os import threading # global variables STOP_REQUESTED = False # some functions that increase readability of the code def readfile(filename): try: return codecs.open(filename, encoding = "utf-8").read() except FileNotFoundError: return False except Exception as e: print( "[ERROR] Unexpected error occured in readfile() ({0})".format(e) ) return False # module object classes class ModuleV1: def __init__(self, path, code, enabled, alias, predefine): self.version = 1 self.enabled = enabled self.code = code self.alias = alias self.path = path self.predefine = predefine if self.predefine: self.set_predefine() # set environmental variables def set_env(self): self.RESPONCE = "" def set_predefine(self): try: exec(self.predefine) except Exception as e: print("[ERROR] module: module \"{}\" ({}) raised exception \"{}\" during predefine stage, disabling it...".format(self.path, self.alias, e)) # running the module def process(self, msg): self.set_env() self.MESSAGE = msg try: exec(self.code) return self.RESPONCE except Exception as e: print("[ERROR] module: module \"{}\" ({}) raised exception \"{}\"".format(self.path, self.alias, e)) return "" # module control unit class ModuleControlUnit: def __init__(self): self.modules = [] self.reload_modules() print("[INFO] ModuleControlUnit: initialized successfully") def reload_modules(self): for folder in os.listdir("modules/"): try: meta_raw = readfile("modules/{}/meta.json".format(folder)) if not meta_raw: print("[WARN] module_loader: no meta.json found in module folder \"{}\"".format(folder)) continue meta = json.loads( meta_raw ) if "version" in meta: if meta["version"] == 1: if "index_file" in meta: index_file = meta["index_file"] else: index_file = "index.py" code = readfile( "modules/{}/{}".format(folder, index_file) ) if not code: # False both when readfile() returns False and when the code string is empty print("[WARN] reload_modules: module {} does not have any code, skipping...".format(folder)) continue if "start_on_boot" in meta: enabled = meta["start_on_boot"] else: enabled = False if "alias" in meta: alias = meta["alias"] else: alias = None if "predefine" in meta: predefine = readfile("modules/{}/{}".format(folder, meta["predefine"])) else: predefine = False self.modules.append( ModuleV1( "modules/{}/".format(folder), code, enabled, alias, predefine ) ) print("[INFO] reload_modules: successfully loaded {} as {} (start_on_boot: {})".format(folder, alias, enabled)) else: print("[WARN] reload_modules: module {} requires unsupported version ({} > 1), skipping...".format(folder, meta["version"])) except Exception as e: print("[ERROR] module_loader: error while loading module \"{}\" ({})".format(folder, e)) def reload_module(self, folder_name): try: meta_raw = readfile("modules/{}/meta.json".format(folder)) if not meta_raw: print("[WARN] module_loader: no meta.json found in module folder \"{}\"".format(folder_name)) return 1 meta = json.loads( readfile("modules/{}/meta.json".format(folder_name)) ) if "version" in meta: if meta["version"] == 1: if "index_file" in meta: index_file = meta["index_file"] else: index_file = "index.py" code = readfile( "modules/{}/{}".format(folder_name, index_file) ) if not code: # False both when readfile() returns False and when the code string is empty print("[WARN] reload_modules: module {} does not have any code, skipping...".format(folder_name)) if "start_on_boot" in meta: enabled = meta["start_on_boot"] else: enabled = False if "alias" in meta: alias = meta["alias"] else: alias = None self.modules.append( ModuleV1( code, enabled, alias ) ) print("[INFO] reload_modules: successfully loaded {} as {} (start_on_boot: {})".format(folder_name, alias, enabled)) else: print("[WARN] reload_modules: module {} requires unsupported version ({} > 1), skipping...".format(folder_name, meta["version"])) except Exception as e: print("[ERROR] module_loader: error while loading module \"{}\" ({})".format(folder_name, e)) # message queue object to go back to synchronous message processing #class MessageQueue: # def __init__(self): # print("[INFO] Initializing the message queue...") # self.queue = [] # synchronous message processor def queue_processor(): while True: try: if len(message_queue) > 0: msg = message_queue[0] print("[DEBUG] queue_processor: {}".format(msg)) # debug # check for control commands if msg["chat"]["id"] == 575246355: if msg["text"][0] == "$": command = msg["text"][1:].split(" ") if len(command) >= 2 and command[0] == "module": if command[1] == "reload": print("[INFO] Module reloading triggered by a command") del mcu.modules[:] mcu.reload_modules() del message_queue[0] continue # modules are used in here for mod in mcu.modules: if mod.enabled: if mod.version == 1: responce = mod.process(msg) if len(responce) > 0: updater.bot.send_message(chat_id = msg.chat.id, text = responce, disable_web_page_preview = True) print("Responded using module {} ({}) with text: {}".format(mod.path, mod.alias, responce)) break del message_queue[0] time.sleep(0.1) else: if STOP_REQUESTED: break else: time.sleep(1) except Exception as e: print("[ERROR] queue_processor: current message queue: {}".format(message_queue)) print("[ERROR] queue_processor: error while processing message, trying to delete it...") try: del message_queue[0] print("[INFO] queue_processor: deleted broken message from the queue") except: print("[WARN] queue_processor: message seems absent, whatever") print("[INFO] queue_processor thread stops successfully") # telegram bot processor def message_handler(update, context): print("[DEBUG] Received new message") # just for testing message_queue.append(update.message) # --- Final stage --- # initializing services and queues message_queue = [] mcu = ModuleControlUnit() processor_thread = threading.Thread( target = queue_processor, args = [] ) processor_thread.start() # connecting to Telegram servers and listening for messages TOKEN = readfile("config/token") if not TOKEN: print("[CRIT] Token has not been defined, quitting") sys.exit(1) # connect to Telegram servers updater = Updater(TOKEN, use_context = True) dispatcher = updater.dispatcher # assign the handler for messages dispatcher.add_handler(MessageHandler(Filters.text, message_handler)) # run the bot updater.start_polling() updater.idle()